接上一篇,讓我們來繼續討論二叉查找樹的基本操作,需要注意的一點是,上篇中的遍歷操作是針對任意一棵二叉樹都可以的,凡是沒提及需要二叉查找樹性質的地方,應該都是滿足二叉樹的操作。上篇主要討論了二叉樹的遍歷操作,這篇,我們來討論二叉查找樹的查找、求最大最小值以及求前驅和后繼等操作。
- 查找
二叉查找樹的查找操作可以在O(h)時間內完成,其中h為樹的高度。查找的算法很簡單,根據二叉查找樹的性質,我們先將要比較的元素跟根元素相比較,如果相等則返回,如果大於根結點的key,則繼續在右子樹中查找,如果小於根結點的key值,則在左子樹中查找。這也跟插入過程類似,童鞋們可以想象一下,我就不畫圖了,畫圖很麻煩。
下面的代碼完成了在樹根為x的樹中查找關鍵字為k的元素,如果存在的話就返回其飲用,不存在,則返回null。
遞歸查找的代碼為:
1 /** 2 * 查找以x為根結點的樹中key的值為k的結點,返回找到的結點或者null 3 * @author Alfred 4 * @param x 根結點 5 * @param k 要查找的整數 6 * @return 找到的結點或者null 7 */ 8 private TreeNode treeSearch(TreeNode x, int k){ 9 if(x == null || k == x.getKey()){ 10 return x; 11 } 12 if(k < x.getKey()){ 13 return treeSearch(x.getLeft(), k);//查左子樹 14 }else{ 15 return treeSearch(x.getRight(), k);//查右子樹 16 } 17 }
非遞歸查找的代碼為:
1 /** 2 * 非遞歸地查找以x為根結點的樹中key的值為k的結點,返回找到的結點或者null 3 * @author Alfred 4 * @param x 根結點 5 * @param k 要查找的整數 6 * @return 找到的結點或者null 7 */ 8 private TreeNode treeSearchNonrecursive(TreeNode x, int k){ 9 while(x != null && k != x.getKey()){ 10 if(k < x.getKey()){ 11 x = x.getLeft(); 12 }else{ 13 x = x.getRight(); 14 } 15 } 16 return x; 17 }
- 最大值
根據二叉查找樹的性質,樹中的最大值一定是位於整棵樹的最“右”邊的右孩子,因為,二叉查找樹的性質中說明了,右子樹中的結點都大於或者等於父結點和左子樹。所以求最大值是一個很簡單的操作。代碼如下:
1 /** 2 * 找以x為根結點的二叉查找樹中的最大值 3 * @author Alfred 4 * @param x 根結點 5 * @return 最大值結點或者null 6 */ 7 public TreeNode treeMax(TreeNode x){ 8 while(x.getRight() != null){ 9 x = x.getRight(); 10 } 11 return x; 12 }
- 最小值
同理,根據二叉查找樹的性質,最小值一定是位於整棵樹中最“左”邊的左孩子,因為,二叉查找樹的性質的性質中說明了,左子樹中的結點都小於或者等於父結點和右子樹。所以跟求最大值對偶的代碼如下:
1 /** 2 * 找以x為根結點的二叉查找樹中的最小值 3 * @author Alfred 4 * @param x 根結點 5 * @return 最小值結點或者null 6 */ 7 public TreeNode treeMin(TreeNode x){ 8 while(x.getLeft() != null){ 9 x = x.getLeft(); 10 } 11 return x; 12 }
- 后繼
由於在之前的博客里面數結點的定義形式,在處理的時候將樹中相同的結點全都合並了起來,因此,位於樹中不同位置的結點的key值肯定是不同的。因此,我們將求某一個結點后繼結點的操作看成是求大於該結點key值的結點中key值最小的那個結點(有點繞。。。)。
我們記結點x為輸入結點,y為輸出結點,即y結點是x結點的后繼,在我們這里分兩種情況進行討論:
1. x結點的右子樹不為空。
根據二叉查找樹的性質,y肯定是位於x的右子樹上,而且是x的右子樹的最小值。
2. x結點的右子樹為空。
如果存在后繼y,則y是x的最低祖先結點,且y的左兒子也是x的祖先。暈了?說的簡單些。如果x結點的右子樹為空,則以x結點為最“右”孩子的子樹t中,x結點一定是這個子樹的最大值,根據二叉查找樹的性質,只有當存在某一結點y,y的左子樹恰好是t的時候,y才是x的后繼。現在回去讀一讀這段開始的話,是不是容易理解多了。
好了,了解了基本的算法,就讓我貼上代碼吧~
1 /** 2 * 找結點x的后繼結點 3 * @author Alfred 4 * @param x 結點 5 * @return x的后繼結點或者null 6 */ 7 public TreeNode treeSuccessor(TreeNode x){ 8 //第一種情況 9 if(x.getRight() != null){ 10 return treeMin(x.getRight()); 11 } 12 //第二種情況 13 TreeNode tmpNode = x.getParent(); 14 while(tmpNode != null && x == tmpNode.getRight()){ 15 x = tmpNode; 16 tmpNode = tmpNode.getParent(); 17 } 18 return tmpNode; 19 }
這個算法就是按照上面提到的兩種情況來實現的,時間復雜度為O(h),h為樹的高度。
- 前驅
求結點的前驅的算法與后繼的算法是對稱的。其時間復雜度也是O(h)。在處理上也分為兩種情況,我就直接上代碼了,有心的童鞋們自己想一下吧~
1 /** 2 * 找結點x的前趨結點 3 * @author Alfred 4 * @param x 結點 5 * @return x的前趨結點或者null 6 */ 7 public TreeNode treePredecessor(TreeNode x){ 8 //第一種情況 9 if(x.getLeft() != null){ 10 return treeMax(x.getLeft()); 11 } 12 //第二種情況 13 TreeNode tmpNode = x.getParent(); 14 while(tmpNode != null && x == tmpNode.getLeft()){ 15 x = tmpNode; 16 tmpNode = tmpNode.getParent(); 17 } 18 return tmpNode; 19 }
嗯。。。本話完,其他操作下回分解。