|
| 1 | +### 二叉查找树,又称二叉排序树, |
| 2 | +### 一、性质: |
| 3 | +#### (1)若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值 |
| 4 | +#### (2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值 |
| 5 | +#### (3)它的左右子树也分别为二叉排序树。 |
| 6 | +### 它的构建详情见《大话数据结构》P315页 |
| 7 | + |
| 8 | +#### 定义二叉排序树先: |
| 9 | +``` |
| 10 | +public class SearchTree { |
| 11 | + |
| 12 | + public TreeNode root; //根结点 |
| 13 | + |
| 14 | + public long size; //长度 |
| 15 | +} |
| 16 | +``` |
| 17 | +``` |
| 18 | +public class TreeNode { |
| 19 | + |
| 20 | + public int data; |
| 21 | + |
| 22 | + /*该节点的父节点*/ |
| 23 | + public TreeNode parent; |
| 24 | + |
| 25 | + /*该节点的左子节点*/ |
| 26 | + public TreeNode left; |
| 27 | + |
| 28 | + /*该节点的右子节点*/ |
| 29 | + public TreeNode right; |
| 30 | + |
| 31 | + public TreeNode(int data) { |
| 32 | + this.data = data; |
| 33 | + |
| 34 | + } |
| 35 | + |
| 36 | + @Override |
| 37 | + public String toString() { |
| 38 | + return "TreeNode [data=" + data + "]"; |
| 39 | + } |
| 40 | + |
| 41 | +} |
| 42 | +``` |
| 43 | +### 二、二叉排序树查找操作: |
| 44 | +#### 大致操作: |
| 45 | +#### (1)假定二叉排序树的根结点指针为 root ,给定的关键字值为 K , |
| 46 | +#### (2)置初值: q = root |
| 47 | +#### (3)如果 K = q -> key ,则查找成功,算法结束 |
| 48 | +#### (4)否则,如果 K < q -> key ,而且 q 的左子树非空,则将 q 的左子树根送 q ,转步骤2;否则,查找失败,结束算法; |
| 49 | +#### (5) 否则,如果 K > q -> key ,而且 q 的右子树非空,则将 q 的右子树根送 q ,转步骤2;否则,查找失败,算法结束。 |
| 50 | + |
| 51 | +``` |
| 52 | +public Node findNode(int data){ |
| 53 | + if(root==null){ |
| 54 | + return null; |
| 55 | + } |
| 56 | + TreeNode currentNode = root;//把根结点给到currentNode,由它开始递归 |
| 57 | + while(currentNode!=null){ |
| 58 | + if(currentNode.data>data){//大于data就往左子树递归找 |
| 59 | + currentNode = currentNode.left; |
| 60 | + }else if(currentNode.data<data){//小于data就往右子树找 |
| 61 | + currentNode = currentNode.right; |
| 62 | + }else{//找到了就返回! |
| 63 | + return currentNode; |
| 64 | + } |
| 65 | + } |
| 66 | + return null; |
| 67 | + } |
| 68 | +``` |
| 69 | +### 三、插入操作: |
| 70 | +#### 在二叉排序树中插入新结点,要保证插入后的二叉树仍符合二叉排序树的定义。 |
| 71 | +#### 大致过程: |
| 72 | +#### (1)若二叉排序树为空,则待插入结点S作为根结点插入到空树中; |
| 73 | +#### (2)当非空时,将待插结点关键字S->key和树根关键字t->key进行比较,若s->key = t->key,则无须插入,若s->key< t->key,则插入到根的左子树中,若s->key> t->key,则插入到根的右子树中。而子树中的插入过程和在树中的插入过程相同,如此进行下去,直到把结点*s作为一个新的树叶插入到二叉排序树中,或者直到发现树已有相同关键字的结点为止。 |
| 74 | +``` |
| 75 | +public Boolean addTreeNode(int data){ |
| 76 | + if(null==root){ |
| 77 | + root = new TreeNode(data); |
| 78 | + return true; |
| 79 | + } |
| 80 | + TreeNode treeNode = new TreeNode(data);// 即将被插入的数据 |
| 81 | + TreeNode currentNode = root; |
| 82 | + TreeNode parentNode; //暂存父结点,避免遍历时操作干扰到原来的树结构 |
| 83 | + while(true){ |
| 84 | + parentNode = currentNode;// 保存父节点 |
| 85 | + // 插入的数据比父节点小 |
| 86 | + if(currentNode.data>data){ |
| 87 | + currentNode = currentNode.left;//表示到父结点的左子节点 |
| 88 | + if(null==currentNode){ |
| 89 | + parentNode.left = treeNode;//把刚刚分配好的节点给到左子节点 |
| 90 | + treeNode.parent = parentNode;//把刚刚分配好的节点中的父指针指向了父结点 |
| 91 | + size++; |
| 92 | + System.out.println("数据成功插入到二叉查找树中"); |
| 93 | + return true; |
| 94 | + } |
| 95 | + }else if(currentNode.data<data){ |
| 96 | + currentNode = currentNode.right;//表示到父结点的右子节点 |
| 97 | + // 当前父节点的右子节点为空 |
| 98 | + if (null == currentNode) { |
| 99 | + parentNode.right = treeNode;//把刚刚分配好的节点给到右子节点 |
| 100 | + treeNode.parent = parentNode;//把刚刚分配好的节点中的父指针指向了父结点 |
| 101 | + System.out.println("数据成功插入到二叉查找树中"); |
| 102 | + size++; |
| 103 | + return true; |
| 104 | + } |
| 105 | + }else{ |
| 106 | + System.out.println("输入数据与节点的数据相同,不再插入"); |
| 107 | + return false; |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | +``` |
| 112 | +### 四、删除操作: |
| 113 | +#### 假设被删结点是*p,其双亲是*f,不失一般性,设*p是*f的左孩子,下面分三种情况讨论: |
| 114 | +#### 若结点*p是叶子结点,则只需修改其双亲结点*f的指针即可。也就是让它双亲节点的子节点指针至null即可。 |
| 115 | +#### 若结点*p只有左子树PL或者只有右子树PR,则只要使PL或PR 成为其双亲结点的左子树即可。也就是把该节点的子节点对应上该节点的双亲节点。 |
| 116 | +#### 若结点*p的左、右子树均非空,先找到*p的中序前趋(或后继)结点*s(注意*s是*p的左子树中的最右下的结点,它的右链域为空),然后有两种做法:1 令*p的左子树直接链到*p的双亲结点*f的左链上,而*p的右子树链到*p的中序前趋结点*s的右链上。2 以*p的中序前趋结点*s代替*p(即把*s的数据复制到*p中),将*s的左子树链到*s的双亲结点*q的左(或右)链上。也就是找它的一个子节点去代替被它自己的位置! |
| 117 | +``` |
| 118 | +//当然在查之前还有个操作,那就是查找啦!!!接着才是删除的节点有三种,叶子节点,有一个节点的节点,有两个节点的节点。 |
| 119 | +public boolean delete(int data){ |
| 120 | + if(root==null){ |
| 121 | + return false; |
| 122 | + } |
| 123 | + TreeNode currentNode = root;//标记当前结点 |
| 124 | + TreeNode parent = root;//标记当前节点的父节点 |
| 125 | + // |
| 126 | + boolean isLeftChild = false;// 这里主要是为了区分当前要删除的是左孩子还是右孩子 |
| 127 | + // 显然,当current.iData == key 时,current就是需要删除的节点 |
| 128 | + // 在循环中利用parent保存了父类节点,也就是以parent为父节点一层层去查 |
| 129 | + while(currentNode.getData()!=data){ |
| 130 | + parent = currentNode;// 在循环中利用parent保存了父类节点 |
| 131 | + if(data < currentNode.data){//根据二叉查找树特点去循环,小的在左子树 |
| 132 | + isLeftChild = true; |
| 133 | + currentNode = currentNode.left; |
| 134 | + }else{ |
| 135 | + isLeftChild = false; |
| 136 | + currentNode = currentNode.right; |
| 137 | + } |
| 138 | + if(currentNode == null){// 找不到data时返回false |
| 139 | + return false; |
| 140 | + } |
| 141 | + } |
| 142 | + //找到后的操作:!!! |
| 143 | + // 当节点为叶子节点的时候 |
| 144 | + if(currentNode.left==null&¤tNode.right==null){ |
| 145 | + if(currentNode == root){//如果删的是root节点 |
| 146 | + root = null; |
| 147 | + }else if(isLeftChild){//如果是左子结点 |
| 148 | + parent.left =null; |
| 149 | + }else{ |
| 150 | + parent.right = null; |
| 151 | + } |
| 152 | + // 当删除的节点为含有一个子节点的节点 |
| 153 | + // 删除的节点只有一个左子节点时 |
| 154 | + // 必须要考虑被删除的节点是左节点还是右节点 |
| 155 | + }else if(currentNode.right==null){ |
| 156 | + if(currentNode == root){// 要删除的节点为根节点 |
| 157 | + root = currentNode.left; |
| 158 | + }else if(isLeftChild){// 要删除的节点是一个左子节点 |
| 159 | + parent.left=currentNode.left; |
| 160 | + }else{// 要删除的节点是一个右子节点 |
| 161 | + parent.right = currentNode.left; |
| 162 | + } |
| 163 | + // 当删除的节点为含有一个子节点的节点 |
| 164 | + // 删除的节点只有一个右子节点时 |
| 165 | + // 必须要考虑被删除的节点是左节点还是右节点 |
| 166 | + }else if(currentNode.left==null){ |
| 167 | + if(root == currentNode){ //同上 |
| 168 | + root = currentNode.right; |
| 169 | + }else if(isLeftChild){ |
| 170 | + parent.left = currentNode.right; |
| 171 | + }else{ |
| 172 | + parent.right = currentNode.right; |
| 173 | + } |
| 174 | + }else{ |
| 175 | + //首先要获取被删除节点的后继节点,currentNode。也就是说getNode里面的树形结构已经删除了的,排好序了的,(只是右边连接上,左边没有连接上)给到一个暂存Node successor那里 |
| 176 | + Node successor = getNode(currentNode); |
| 177 | + if(current == root){ |
| 178 | + root = successor ; |
| 179 | + //这里已经屏蔽了后继节点是叶子和非叶子节点 |
| 180 | + }else if(isLeftChild){//如果被删除的是左子节点 |
| 181 | + parent.left=successor; |
| 182 | + }else{//被删除的是右子节点 |
| 183 | + parent.right=successor; |
| 184 | + } |
| 185 | + //最终步骤,把排好了右边序的树形结构对应上原来的左子树! |
| 186 | + successor.left=currentNode.left; |
| 187 | + |
| 188 | + } |
| 189 | + return true; |
| 190 | + |
| 191 | + } |
| 192 | + // 寻找后继节点,主要是当要删除的节点包含了两个子节点的时候 |
| 193 | + // 返回后继节点,后继节点就是比要删除的节点的关键值要大的节点集合中的最小值。 |
| 194 | + //后继节点要么是被删除节点的不包含左子节点的右节点,要么就是包含左子节点的右节点的子节点 |
| 195 | + public TreeNode getNode(TreeNode delNode){ |
| 196 | + // 后继节点的父节点 |
| 197 | + TreeNode successorParent = delNode; |
| 198 | + // 后继节点 |
| 199 | + TreeNode successor = delNode.getRight(); |
| 200 | + //判断后继节点是否有左孩子 |
| 201 | + TreeNode current = successor.getLeft(); |
| 202 | + while (current != null) { |
| 203 | + successorParent = successor;//循环把后继节点往后移动(往左下移),直到后继结点的左子树为空。 |
| 204 | + successor = current; |
| 205 | + current = current.getLeft(); |
| 206 | + } |
| 207 | + //当该后继节点是属于包含左子节点的右节点的子节点。把后继结点的右子树给它的父节点,然后把后继结点移动到被删除节点位置,去替代它。因为上面的遍历后继节点的左子树必然为null |
| 208 | + if (successor != delNode.right){ |
| 209 | + //后继节点的右子树给它自己的父节点(交待后事,因为自己要闪人) |
| 210 | + successorParent.left(successor.right); |
| 211 | + //把确立的后继节点连接被删除节点的右孩子 |
| 212 | + successor.right(delNode.right); |
| 213 | + } |
| 214 | + return successor; |
| 215 | + } |
| 216 | +``` |
| 217 | +#### 删除例子: |
| 218 | +#### 要删除的是37,结果是可以拿它的后继48来替代位置!!我们拿着图对着代码来看就懂了!! |
| 219 | + |
| 220 | + |
0 commit comments