二叉搜索树、二叉排序树(查找、插入和删除)——Java版本
1. 概念二叉搜索树又称二叉排序树它或者是一棵空树或者是具有以下性质的二叉树:若它的左子树不为空则左子树上所有节点的值都小于根节点的值若它的右子树不为空则右子树上所有节点的值都大于根节点的值它的左右子树也分别为二叉搜索树不允许存在相同的结点如一个int a [] {5,3,4,1,7,8,2,6,0,9}; 数组组成二叉排序树定义二叉搜索树的结构static class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode(int val) { this.val val; } } public TreeNode root;2. 操作 - 查找/* 搜索 搜索的时间复杂度最好O(logN) 搜索的时间复杂度最坏O(N) */ public boolean search(int key){ TreeNode cur root; while(cur ! null){ //找到key返回true if(cur.val key) { return true; } //到右树寻找 else if(cur.val key){ cur cur.right; } //到左树寻找 else{ cur cur.left; } } //没有找到值为key的结点 return false; }3. 操作 - 插入1 如果树为空树即根 null直接插入2如果树不是空树按照查找逻辑确定插入位置插入新结点/* 插入都是插入到叶子结点 插入的时间复杂度最好O(logN) 插入的时间复杂度最坏O(N) */ public void insert(int val) throws Exception { TreeNode newNode new TreeNode(val); if (root null) { root newNode; return; } TreeNode cur root; TreeNode parent null; while (cur ! null) { parent cur; //判断是否有重复元素进入 if (cur.val val) { throw new Exception(有重复元素进入); } //如果要插入的结点的值大于当前结点就应该在cur的右子树 if (cur.val val) { cur cur.right; } //如果要插入的结点的值小于当前结点就应该在cur的左子树 else if (cur.val val) { cur cur.left; } } //如果要插入的结点小于父节点就应该接在父节点的左子树 if (parent.val val) { parent.left newNode; } //如果要插入的结点大于父节点就应该接在父节点的右子树 else { parent.right newNode; } }搜索和插入的运行结果4. 操作 - 删除 ⭐⭐⭐⭐⭐分情况讨论如下所示设待删除结点为 cur, 待删除结点的双亲结点为 parent1cur.left null① cur 是 root则root cur.right② cur 不是 rootcur 是 parent.left则parent.left cur.right③ cur 不是 rootcur 是 parent.right则parent.right cur.right2cur.right null① cur 是 root则root cur.left② cur 不是 rootcur 是 parent.left则parent.left cur.left③ cur 不是 rootcur 是 parent.right则parent.right cur.left3cur.left ! null cur.right ! null需要使用替换法进行删除即在它的右子树中寻找中序下的第一个结点(关键码最小)用它的值填补到被 删除节点中再来处理该结点的删除问题。这部分的代码为有bug//3.cur两边的子树都不为空 else{ TreeNode t cur.left; TreeNode tp null; while(t.right ! null){ tp t; t t.right; } cur.val t.val; //这个就变成了删除t指向的结点且t结点的右子树为空 tp.right t.left; }原来的图第一步进行分析和改进第二步进行分析和改进树类的方法//删除结点的操作 public void remove(int val) { TreeNode parent null; TreeNode cur root; while (cur ! null) { //去右子树寻找 if (cur.val val) { parent cur; cur cur.right; } else if (cur.val val) { parent cur; cur cur.left; } //找到了 else { removeNode(cur, parent); return; } } } private void removeNode(TreeNode cur, TreeNode parent) { //1.cur的左子树为空 if (cur.left null) { //1cur 是 root则 root cur.right if (cur root) { root cur.right; } //2cur 不是 rootcur 是 parent.left则 parent.left cur.right else if (cur parent.left) { parent.left cur.right; } //3cur 不是 rootcur 是 parent.right则 parent.right cur.right else { parent.right cur.right; } } //2.cur的右子树为空 else if (cur.right null) { //1cur 是 root则 root cur.left if (cur root) { root cur.left; } //2cur 不是 rootcur 是 parent.left则 parent.left cur.left else if (cur parent.left) { parent.left cur.right; } //3cur 不是 rootcur 是 parent.right则 parent.right cur.left else { parent.right cur.right; } } //3.cur两边的子树都不为空 else { TreeNode t cur.left; TreeNode tp cur; while (t.right ! null) { tp t; t t.right; } cur.val t.val; if (cur tp) { tp.left t.left; } else { //这个就变成了删除t指向的结点且t结点的右子树为空 tp.right t.left; } } }测试类public class Test { public static void main(String[] args) throws Exception { BinarySearchTree binarySearchTree new BinarySearchTree(); binarySearchTree.insert(5); binarySearchTree.insert(3); binarySearchTree.insert(7); binarySearchTree.insert(1); binarySearchTree.insert(4); binarySearchTree.insert(6); binarySearchTree.insert(8); binarySearchTree.insert(0); binarySearchTree.insert(9); binarySearchTree.remove(3); } }5. 二叉搜索树的完整代码public class BinarySearchTree { static class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode(int val) { this.val val; } } public TreeNode root; /* 搜索 搜索的时间复杂度最好O(logN) 搜索的时间复杂度最坏O(N) */ public boolean search(int key) { TreeNode cur root; while (cur ! null) { //找到key返回true if (cur.val key) { return true; } //到右树寻找 else if (cur.val key) { cur cur.right; } //到左树寻找 else { cur cur.left; } } //没有找到值为key的结点 return false; } /* 插入都是插入到叶子结点 插入的时间复杂度最好O(logN) 插入的时间复杂度最坏O(N) */ public void insert(int val) throws Exception { TreeNode newNode new TreeNode(val); if (root null) { root newNode; return; } TreeNode cur root; TreeNode parent null; while (cur ! null) { parent cur; //判断是否有重复元素进入 if (cur.val val) { throw new Exception(有重复元素进入); } //如果要插入的结点的值大于当前结点就应该在cur的右子树 if (cur.val val) { cur cur.right; } //如果要插入的结点的值小于当前结点就应该在cur的左子树 else if (cur.val val) { cur cur.left; } } //如果要插入的结点小于父节点就应该接在父节点的左子树 if (parent.val val) { parent.left newNode; } //如果要插入的结点大于父节点就应该接在父节点的右子树 else { parent.right newNode; } } //删除结点的操作 public void remove(int val) { TreeNode parent null; TreeNode cur root; while (cur ! null) { //去右子树寻找 if (cur.val val) { parent cur; cur cur.right; } else if (cur.val val) { parent cur; cur cur.left; } //找到了 else { removeNode(cur, parent); return; } } } private void removeNode(TreeNode cur, TreeNode parent) { //1.cur的左子树为空 if (cur.left null) { //1cur 是 root则 root cur.right if (cur root) { root cur.right; } //2cur 不是 rootcur 是 parent.left则 parent.left cur.right else if (cur parent.left) { parent.left cur.right; } //3cur 不是 rootcur 是 parent.right则 parent.right cur.right else { parent.right cur.right; } } //2.cur的右子树为空 else if (cur.right null) { //1cur 是 root则 root cur.left if (cur root) { root cur.left; } //2cur 不是 rootcur 是 parent.left则 parent.left cur.left else if (cur parent.left) { parent.left cur.right; } //3cur 不是 rootcur 是 parent.right则 parent.right cur.left else { parent.right cur.right; } } //3.cur两边的子树都不为空 else { TreeNode t cur.left; TreeNode tp cur; while (t.right ! null) { tp t; t t.right; } cur.val t.val; if (cur tp) { tp.left t.left; } else { //这个就变成了删除t指向的结点且t结点的右子树为空 tp.right t.left; } } } }6. 性能分析插入和删除操作都必须先查找查找效率代表了二叉搜索树中各个操作的性能。 对有n个结点的二叉搜索树若每个元素查找的概率相等则二叉搜索树平均查找长度是结点在二叉搜索树的深度 的函数即结点越深则比较次数越多。 但对于同一个关键码集合如果各关键码插入的次序不同可能得到不同结构的二叉搜索树最优情况下二叉搜索树为完全二叉树其平均比较次数为最差情况下二叉搜索树退化为单支树其平均比较次数为N问题如果退化成单支树二叉搜索树的性能就失去了。那能否进行改进不论按照什么次序插入关键码都可以是二叉搜索树的性能最佳答可以这就涉及到后面的AVL树和红黑树了后期的文章会继续讨论AVL树和红黑树。7. 和Java类集的关系TreeMap 和 TreeSet 即 Java 中利用搜索树实现的 Map 和 Set实际上用的是红黑树而红黑树是一棵近似平衡的二叉搜索树即在二叉搜索树的基础之上 颜色以及红黑树性质验证关于红黑树的内容后序再进行讲解。