二叉查找树(五)
接上一篇,继续讲二叉查找树的操作,之前的博客都讲得差不多了,本篇就讲一下删除操作,以及求最矮公共父结点(LCA:lowest common ancestor)的操作吧。
- 删除
将一个结点从二叉查找树中删除之后,剩下的结点可能会不满足二叉查找树的性质,因此,在删除结点之后要对树进行调整,使其满足二叉查找树的性质。根据结点的孩子的数量,将删除操作分为三种情况,我们记要删除的结点为z,实际上删除的结点为y。
1. z结点没有孩子。
如下图a所示,我们要删除值为13的结点,因为结点没有孩子,所以删除之后不会影响到二叉树的整体性质,也就是说,直接将13这个结点删除即可,如图a所示,从左边的二叉树删除13这个点之后变到右边的二叉树。
2. z结点有一个孩子。
如下图b所示,要删除的值为16的结点有一个孩子,而且是右孩子,那么从图上来看,如果,我们将16去掉,然后把以20为结点的子树作为15的右子树,那么整棵树还是符合二叉查找树的性质的,因此,有一个孩子的结点的删除操作,就是要将其孩子作为其父结点的孩子即可。如图b所示。
3. z结点有两个孩子。
如下图c所示,要删除的值为5的结点,有两个孩子,删除之后肯定整棵树就不符合二叉查找树的性质了,因此要进行调整,我们发现,将5的后继,值为6的结点来放到5的位置,然后将6的孩子7作为6的父结点10的孩子,如下图c所示,我们要删除的是z结点,而我们实际要删除y结点,并替换z结点。这里需要注意的一点是,如果一个结点有右孩子,则该结点的后继,至多有一个子女,而且是右孩子。因为假如该结点的后继有左孩子和右孩子,那么其左孩子的值肯定是介于该结点和其后继之间的,那么按照二叉查找树的性质,这个左孩子就应该是该结点的后继,所以,这与原先的后继相互矛盾,因此,结论成立。
好了,分析完了删除的三种情况,我们来完成我们的程序吧。
1 /** 2 * 删除二叉查找树中的结点z 3 * @author Alfred 4 * @param z 要删除的结点 5 * @return 删除或者替换的结点 6 */ 7 public TreeNode treeDelete(TreeNode z){ 8 TreeNode x = null, y = null; 9 if(z.getLeft() == null || z.getRight() == null){ 10 //对应第1和第2种情况 11 y = z; 12 }else{ 13 //对应第3种情况 14 y = treeSuccessor(z); 15 } 16 //将x置为y的非null子女,或者当y无子女时置为null 17 if(y.getLeft() != null){ 18 x = y.getLeft(); 19 }else{ 20 x = y.getRight(); 21 } 22 //通过修改x和y的父结点的引用,将y删除 23 if(x != null){ 24 x.setParent(y.getParent()); 25 } 26 if(y.getParent() == null){ 27 //x成为树根 28 rootNode = x; 29 }else if(y == y.getParent().getLeft()){ 30 //y是其父结点的左孩子 31 y.getParent().setLeft(x); 32 }else{ 33 //y是其父结点的右孩子 34 y.getParent().setRight(x); 35 } 36 //如果y和z不是同一个结点,说明是第3种情况 37 if(y != z){ 38 //内容替换 39 z.setKey(y.getKey()); 40 z.setDataNum(y.getDataNum()); 41 } 42 return y; 43 }
上面的程序完整的搞定了上面分析的三种情况。
- LCA
LCA问题对于二叉查找树来说是非常简单的。因为二叉查找树满足其特有的性质。给定树中的两个结点x和y,求其最低公共父结点。我们把这个算法分为三种情况。对于一棵二叉查找树来说:
1. 如果x和y分别位于某结点z的左右子树上,那么结点z就是x和y的lca。
2. 如果x和y位于某结点z的左子树或者右子树上,那么x和y的lca也必然位于其所处的左子树或者右子树上。
3. 如果x或者y中,有一个结点是另外一个结点的父结点,那么lca就是它们中的父结点。即如果有一个点和某结点重合,则重合的点就是lca。
那么,我们就从根结点开始,按照从上往下的顺序查找lca吧,比较根结点与两结点的关系,然后再进行下一步的操作。代码如下:
1 /** 2 * 求两个结点的最低公共父结点 3 * @author Alfred 4 * @param x 结点 5 * @param y 结点 6 * @return 最低公共父结点 7 */ 8 public TreeNode lca(TreeNode x, TreeNode y){ 9 TreeNode tmpNode = rootNode; 10 //获取两个key值 11 int xKey = x.getKey(); 12 int yKey = y.getKey(); 13 //给两个元素按从小到大排下序,使得x<y 14 if(xKey > yKey){ 15 int tmpKey = xKey; 16 xKey = yKey; 17 yKey = tmpKey; 18 } 19 while(true){ 20 if(tmpNode.getKey() < xKey){ 21 //这种情况下,lca在其右子树上 22 tmpNode = tmpNode.getRight(); 23 }else if(tmpNode.getKey() > yKey){ 24 //这种情况下,lca在其左子树上 25 tmpNode = tmpNode.getLeft(); 26 }else{ 27 return tmpNode; 28 } 29 } 30 }
代码比较简单,就是按照上面的三种情况按照从上到下的顺序来分类处理的。其他还有一些lca算法,如离线算法(Tarjan)和在线算法(RMQ)等,暂时先不讨论了,以后有时间了再补上吧。为了方便想学习的童鞋来测试,我把整个代码就贴到下面了(木有找到更好地方法来分享。。)。
1 package com.alfred.bstree; 2 3 /** 4 * 二叉查找树结点 5 * @author Alfred 6 */ 7 public class TreeNode { 8 //key值 9 private int key; 10 //记录相同的key值的结点个数 11 private int dataNum; 12 //下面三个大家都懂得! 13 private TreeNode parent; 14 private TreeNode left; 15 private TreeNode right; 16 public TreeNode(int key){ 17 this.key = key; 18 this.dataNum = 1; 19 } 20 public String toString(){ 21 return ""+key+"*"+dataNum+" "; 22 } 23 /** 24 * 偷个懒...自加方法 25 * @author Alfred 26 */ 27 public void incNumByOne(){ 28 this.dataNum++; 29 } 30 31 /** 32 * @return the key 33 */ 34 public int getKey() { 35 return key; 36 } 37 /** 38 * @param key the key to set 39 */ 40 public void setKey(int key) { 41 this.key = key; 42 } 43 /** 44 * @return the dataNum 45 */ 46 public int getDataNum() { 47 return dataNum; 48 } 49 /** 50 * @param dataNum the dataNum to set 51 */ 52 public void setDataNum(int dataNum) { 53 this.dataNum = dataNum; 54 } 55 /** 56 * @return the parent 57 */ 58 public TreeNode getParent() { 59 return parent; 60 } 61 /** 62 * @param parent the parent to set 63 */ 64 public void setParent(TreeNode parent) { 65 this.parent = parent; 66 } 67 /** 68 * @return the left 69 */ 70 public TreeNode getLeft() { 71 return left; 72 } 73 /** 74 * @param left the left to set 75 */ 76 public void setLeft(TreeNode left) { 77 this.left = left; 78 } 79 /** 80 * @return the right 81 */ 82 public TreeNode getRight() { 83 return right; 84 } 85 /** 86 * @param right the right to set 87 */ 88 public void setRight(TreeNode right) { 89 this.right = right; 90 } 91 }
1 package com.alfred.bstree; 2 3 import java.util.LinkedList; 4 import java.util.List; 5 import java.util.Queue; 6 import java.util.Random; 7 import java.util.Stack; 8 9 /** 10 * 二叉查找树 11 * @author Alfred 12 */ 13 public class BSTree { 14 //随机化的构造二叉查找树 15 private Random rand = null; 16 //根结点 17 private TreeNode rootNode = null; 18 19 /** 20 * 以int数组A来创建二叉查找树 21 * @param A int数组 22 */ 23 public BSTree(int[] A){ 24 rand = new Random(); 25 createBSTree(A); 26 } 27 28 /** 29 * 创建二叉查找树 30 * @param A int数组 31 */ 32 private void createBSTree(int[] A){ 33 //先构建一个存储数组下标的List 34 List<Integer> index = new LinkedList<Integer>(); 35 int i = 0; 36 for(i = 0; i < A.length; i++){ 37 index.add(i); 38 } 39 //随机构造二叉树 40 for(i = 0; i < A.length; i++){ 41 int j = 0; 42 if(index.size() > 1){ 43 //随机产生一个数组下标值 44 j = rand.nextInt(index.size() - 1); 45 } 46 //插入二叉树 47 TreeInsert(A[index.get(j)]); 48 //移除下标 49 index.remove(j); 50 } 51 } 52 53 /** 54 * 插入一个整数 55 * @param z 整数 56 */ 57 public void TreeInsert(int z){ 58 TreeNode parentNode = null; 59 TreeNode searchNode = rootNode; 60 TreeNode insertNode = new TreeNode(z); 61 //while循环找到要插入的点的父结点 62 while(searchNode != null){ 63 parentNode = searchNode; 64 if(insertNode.getKey() < searchNode.getKey()){ 65 searchNode = searchNode.getLeft(); 66 }else if(insertNode.getKey() == searchNode.getKey()){ 67 //如果是key值相同的话,直接插入,偷懒在这里... 68 searchNode.incNumByOne(); 69 return; 70 }else{ 71 searchNode = searchNode.getRight(); 72 } 73 } 74 75 insertNode.setParent(parentNode); 76 if(parentNode == null){ 77 rootNode = insertNode; 78 }else if(insertNode.getKey() < parentNode.getKey()){ 79 //插入左结点 80 parentNode.setLeft(insertNode); 81 }else if(insertNode.getKey() == parentNode.getKey()){ 82 //因为上面插入了,所以这里就不会执行了。 83 parentNode.incNumByOne(); 84 System.out.println("this is not supposed to be executed."); 85 }else{ 86 //插入右结点 87 parentNode.setRight(insertNode); 88 } 89 } 90 91 /** 92 * 递归前序遍历以x为根的二叉树 93 * @author Alfred 94 * @param x 根结点 95 */ 96 public void preOrderTreeWalk(TreeNode x){ 97 if(x != null){ 98 System.out.print(x);//访问形式为打印输出一下 99 preOrderTreeWalk(x.getLeft()); 100 preOrderTreeWalk(x.getRight()); 101 } 102 } 103 104 public void preOrderTreeWalk(){ 105 preOrderTreeWalk(rootNode); 106 } 107 108 /** 109 * 非递归前序遍历以x为根结点的二叉树 110 * @author Alfred 111 * @param x 根结点 112 */ 113 public void preOrderTreeWalkNonrecursive1(TreeNode x){ 114 //借助栈来实现。 115 Stack<TreeNode> stack = new Stack<TreeNode>(); 116 while(x != null || !stack.empty()){ 117 if(x != null){ 118 System.out.print(x);//遍历输出 119 stack.push(x);//压栈 120 x = x.getLeft(); 121 }else{ 122 x = stack.pop();//出栈 123 x = x.getRight(); 124 } 125 } 126 } 127 128 /** 129 * 非递归前序遍历以x为根结点的二叉树 130 * @author Alfred 131 * @param x 根结点 132 */ 133 public void preOrderTreeWalkNonrecursive2(TreeNode x){ 134 Stack<TreeNode> stack = new Stack<TreeNode>(); 135 if(x != null){ 136 stack.push(x); 137 while(!stack.empty()){ 138 TreeNode tmpNode = stack.pop(); 139 System.out.print(tmpNode);//遍历输出 140 if(tmpNode.getRight() != null){ 141 stack.push(tmpNode.getRight()); 142 } 143 if(tmpNode.getLeft() != null){ 144 stack.push(tmpNode.getLeft()); 145 } 146 } 147 } 148 } 149 150 public void preOrderTreeWalkNonrecursive(){ 151 System.out.println("方法1:"); 152 preOrderTreeWalkNonrecursive1(rootNode); 153 System.out.println("\n方法2:"); 154 preOrderTreeWalkNonrecursive2(rootNode); 155 } 156 157 /** 158 * 递归中序遍历以x为根的二叉树 159 * @author Alfred 160 * @param x 根结点 161 */ 162 public void inOrderTreeWalk(TreeNode x){ 163 if(x != null){ 164 inOrderTreeWalk(x.getLeft()); 165 System.out.print(x); 166 inOrderTreeWalk(x.getRight()); 167 } 168 } 169 170 public void inOrderTreeWalk(){ 171 inOrderTreeWalk(rootNode); 172 } 173 174 /** 175 * 非递归中序遍历以x为根结点的二叉树 176 * @author Alfred 177 * @param x 根结点 178 */ 179 public void inOrderTreeWalkNonrecursive(TreeNode x){ 180 Stack<TreeNode> stack = new Stack<TreeNode>(); 181 while(x != null || !stack.empty()){ 182 if(x != null){ 183 stack.push(x); 184 x = x.getLeft(); 185 }else{ 186 x = stack.pop(); 187 System.out.print(x);//遍历输出 188 x = x.getRight(); 189 } 190 } 191 } 192 193 public void inOrderTreeWalkNonrecursive(){ 194 inOrderTreeWalkNonrecursive(rootNode); 195 } 196 197 /** 198 * 递归后序遍历以x为根的二叉树 199 * @author Alfred 200 * @param x 根结点 201 */ 202 public void postOrderTreeWalk(TreeNode x){ 203 if(x != null){ 204 postOrderTreeWalk(x.getLeft()); 205 postOrderTreeWalk(x.getRight()); 206 System.out.print(x); 207 } 208 } 209 210 public void postOrderTreeWalk(){ 211 postOrderTreeWalk(rootNode); 212 } 213 /** 214 * 非递归后序遍历以x为根结点的二叉树 215 * @author Alfred 216 * @param x 根结点 217 */ 218 public void postOrderTreeWalkNonrecursive1(TreeNode x){ 219 Stack<TreeNode> stack = new Stack<TreeNode>(); 220 TreeNode prev = null; 221 TreeNode curr = null; 222 if(x != null){ 223 stack.push(x); 224 } 225 while(!stack.empty()){ 226 curr = stack.peek(); 227 if(prev == null || prev.getLeft() == curr || prev.getRight() == curr){ 228 if(curr.getLeft() != null){ 229 stack.push(curr.getLeft());//压左孩子 230 }else if(curr.getRight() != null){ 231 stack.push(curr.getRight());//压右孩子 232 } 233 }else if(curr.getLeft() == prev){ 234 if(curr.getRight() != null){ 235 stack.push(curr.getRight());//压右孩子 236 } 237 }else{ 238 System.out.print(curr);//遍历输出 239 stack.pop(); 240 } 241 prev = curr; 242 } 243 } 244 245 /** 246 * 非递归后序遍历以x为根结点的二叉树 247 * @author Alfred 248 * @param x 根结点 249 */ 250 public void postOrderTreeWalkNonrecursive2(TreeNode x){ 251 Stack<TreeNode> stack = new Stack<TreeNode>(); 252 Stack<TreeNode> output = new Stack<TreeNode>(); 253 TreeNode curr = null; 254 if(x != null){ 255 stack.push(x); 256 } 257 while(!stack.empty()){ 258 curr = stack.pop(); 259 output.push(curr);//存放到输出地栈里面 260 if(curr.getLeft() != null){ 261 stack.push(curr.getLeft());//压左孩子 262 } 263 if(curr.getRight() != null){ 264 stack.push(curr.getRight());//压右孩子 265 } 266 } 267 while(!output.empty()){ 268 TreeNode tmpNode = output.pop(); 269 System.out.print(tmpNode);//打印输出 270 } 271 } 272 273 public void postOrderTreeWalkNonrecursive(){ 274 System.out.println("方法1:"); 275 postOrderTreeWalkNonrecursive1(rootNode); 276 System.out.println("\n方法2:"); 277 postOrderTreeWalkNonrecursive2(rootNode); 278 } 279 /** 280 * 层序遍历二叉树 281 * @author Alfred 282 * @param x 根结点 283 */ 284 public void levelOrderTreeWalk(TreeNode x){ 285 Queue<TreeNode> queue = new LinkedList<TreeNode>(); 286 TreeNode tmpNode = null; 287 if(x != null){ 288 queue.offer(x); 289 } 290 while(!queue.isEmpty()){ 291 tmpNode = queue.poll(); 292 System.out.print(tmpNode);//打印输出 293 if(tmpNode.getLeft() != null){ 294 queue.offer(tmpNode.getLeft());//左孩子入队 295 } 296 if(tmpNode.getRight() != null){ 297 queue.offer(tmpNode.getRight());//右孩子入队 298 } 299 } 300 } 301 public void levelOrderTreeWalk(){ 302 levelOrderTreeWalk(rootNode); 303 } 304 305 306 /** 307 * 查找以x为根结点的树中key的值为k的结点,返回找到的结点或者null 308 * @author Alfred 309 * @param x 根结点 310 * @param k 要查找的整数 311 * @return 找到的结点或者null 312 */ 313 private TreeNode treeSearch(TreeNode x, int k){ 314 // System.out.println("treeSearch:"+x); 315 if(x == null || k == x.getKey()){ 316 return x; 317 } 318 if(k < x.getKey()){ 319 return treeSearch(x.getLeft(), k);//查左子树 320 }else{ 321 return treeSearch(x.getRight(), k);//查右子树 322 } 323 } 324 /** 325 * 非递归地查找以x为根结点的树中key的值为k的结点,返回找到的结点或者null 326 * @author Alfred 327 * @param x 根结点 328 * @param k 要查找的整数 329 * @return 找到的结点或者null 330 */ 331 private TreeNode treeSearchNonrecursive(TreeNode x, int k){ 332 while(x != null && k != x.getKey()){ 333 if(k < x.getKey()){ 334 x = x.getLeft(); 335 }else{ 336 x = x.getRight(); 337 } 338 } 339 return x; 340 } 341 342 public TreeNode treeSearch(int k){ 343 return treeSearch(rootNode, k); 344 } 345 public TreeNode treeSearchNonrecursive(int k){ 346 return treeSearchNonrecursive(rootNode, k); 347 } 348 /** 349 * 找以x为根结点的二叉查找树中的最小值 350 * @author Alfred 351 * @param x 根结点 352 * @return 最小值结点或者null 353 */ 354 public TreeNode treeMin(TreeNode x){ 355 while(x.getLeft() != null){ 356 x = x.getLeft(); 357 } 358 return x; 359 } 360 361 public TreeNode treeMin(){ 362 return treeMin(rootNode); 363 } 364 /** 365 * 找以x为根结点的二叉查找树中的最大值 366 * @author Alfred 367 * @param x 根结点 368 * @return 最大值结点或者null 369 */ 370 public TreeNode treeMax(TreeNode x){ 371 while(x.getRight() != null){ 372 x = x.getRight(); 373 } 374 return x; 375 } 376 377 public TreeNode treeMax(){ 378 return treeMax(rootNode); 379 } 380 /** 381 * 找结点x的后继结点 382 * @author Alfred 383 * @param x 结点 384 * @return x的后继结点或者null 385 */ 386 public TreeNode treeSuccessor(TreeNode x){ 387 //第一种情况 388 if(x.getRight() != null){ 389 return treeMin(x.getRight()); 390 } 391 //第二种情况 392 TreeNode tmpNode = x.getParent(); 393 while(tmpNode != null && x == tmpNode.getRight()){ 394 x = tmpNode; 395 tmpNode = tmpNode.getParent(); 396 } 397 return tmpNode; 398 } 399 400 /** 401 * 找结点x的前趋结点 402 * @author Alfred 403 * @param x 结点 404 * @return x的前趋结点或者null 405 */ 406 public TreeNode treePredecessor(TreeNode x){ 407 //第一种情况 408 if(x.getLeft() != null){ 409 return treeMax(x.getLeft()); 410 } 411 //第二种情况 412 TreeNode tmpNode = x.getParent(); 413 while(tmpNode != null && x == tmpNode.getLeft()){ 414 x = tmpNode; 415 tmpNode = tmpNode.getParent(); 416 } 417 return tmpNode; 418 } 419 420 /** 421 * 删除二叉查找树中的结点z 422 * @author Alfred 423 * @param z 要删除的结点 424 * @return 删除或者替换的结点 425 */ 426 public TreeNode treeDelete(TreeNode z){ 427 TreeNode x = null, y = null; 428 if(z.getLeft() == null || z.getRight() == null){ 429 //对应第1和第2种情况 430 y = z; 431 }else{ 432 //对应第3种情况 433 y = treeSuccessor(z); 434 } 435 //将x置为y的非null子女,或者当y无子女时置为null 436 if(y.getLeft() != null){ 437 x = y.getLeft(); 438 }else{ 439 x = y.getRight(); 440 } 441 //通过修改x和y的父结点的引用,将y删除 442 if(x != null){ 443 x.setParent(y.getParent()); 444 } 445 if(y.getParent() == null){ 446 //x成为树根 447 rootNode = x; 448 }else if(y == y.getParent().getLeft()){ 449 //y是其父结点的左孩子 450 y.getParent().setLeft(x); 451 }else{ 452 //y是其父结点的右孩子 453 y.getParent().setRight(x); 454 } 455 //如果y和z不是同一个结点,说明是第3种情况 456 if(y != z){ 457 //内容替换 458 z.setKey(y.getKey()); 459 z.setDataNum(y.getDataNum()); 460 } 461 return y; 462 } 463 464 /** 465 * 求两个结点的最低公共父结点 466 * @author Alfred 467 * @param x 结点 468 * @param y 结点 469 * @return 最低公共父结点 470 */ 471 public TreeNode lca(TreeNode x, TreeNode y){ 472 TreeNode tmpNode = rootNode; 473 //获取两个key值 474 int xKey = x.getKey(); 475 int yKey = y.getKey(); 476 //给两个元素按从小到大排下序,使得x<y 477 if(xKey > yKey){ 478 int tmpKey = xKey; 479 xKey = yKey; 480 yKey = tmpKey; 481 } 482 while(true){ 483 if(tmpNode.getKey() < xKey){ 484 //这种情况下,lca在其右子树上 485 tmpNode = tmpNode.getRight(); 486 }else if(tmpNode.getKey() > yKey){ 487 //这种情况下,lca在其左子树上 488 tmpNode = tmpNode.getLeft(); 489 }else{ 490 return tmpNode; 491 } 492 } 493 } 494 }
1 package com.alfred.bstree; 2 3 public class testMain { 4 5 public static void main(String[] args) { 6 int[] A = new int[]{15,6,18,3,7,17,20,2,4,13,9,3,18,8,8,8,8,8}; 7 BSTree bsTree = new BSTree(A); 8 System.out.println("前序遍历递归方法:"); 9 bsTree.preOrderTreeWalk(); 10 System.out.println("\n前序遍历非递归方法:"); 11 bsTree.preOrderTreeWalkNonrecursive(); 12 System.out.println("\n中序遍历递归方法:"); 13 bsTree.inOrderTreeWalk(); 14 System.out.println("\n中序遍历非递归方法:"); 15 bsTree.inOrderTreeWalkNonrecursive(); 16 System.out.println("\n后序遍历递归方法:"); 17 bsTree.postOrderTreeWalk(); 18 System.out.println("\n后序遍历非递归方法:"); 19 bsTree.postOrderTreeWalkNonrecursive(); 20 System.out.println("\n层序遍历方法:"); 21 bsTree.levelOrderTreeWalk(); 22 23 System.out.println("\n递归查找:"); 24 System.out.println(bsTree.treeSearch(8)); 25 System.out.println("非递归查找:"); 26 System.out.println(bsTree.treeSearchNonrecursive(8)); 27 28 System.out.println("\n最大值:"); 29 System.out.println(bsTree.treeMax()); 30 System.out.println("最小值:"); 31 System.out.println(bsTree.treeMin()); 32 System.out.println("8的后继:"); 33 System.out.println(bsTree.treeSuccessor(bsTree.treeSearch(8))); 34 System.out.println("8的前驱:"); 35 System.out.println(bsTree.treePredecessor(bsTree.treeSearch(8))); 36 System.out.println("\n最大最小值的LCA:"); 37 System.out.println(bsTree.lca(bsTree.treeMin(), bsTree.treeMax())); 38 39 System.out.println("删除8之后:"); 40 TreeNode eight = bsTree.treeSearch(8); 41 bsTree.inOrderTreeWalk(); 42 System.out.println(); 43 bsTree.treeDelete(eight); 44 bsTree.inOrderTreeWalk(); 45 } 46 }
ps:关于二叉查找树的一些操作先写到这里吧,有不对的地方,请广大博友指正啊。
pss:画图好累啊。。转载请注明。。。