二叉查找树(五)

  接上一篇,继续讲二叉查找树的操作,之前的博客都讲得差不多了,本篇就讲一下删除操作,以及求最矮公共父结点(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)等,暂时先不讨论了,以后有时间了再补上吧。为了方便想学习的童鞋来测试,我把整个代码就贴到下面了(木有找到更好地方法来分享。。)。

TreeNode.java
 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 }
BSTree.java
  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 }
TestMain.java
 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:画图好累啊。。转载请注明。。。

posted @ 2012-05-12 09:28  Mr. Coding  阅读(2406)  评论(3编辑  收藏  举报