整體思路:二叉查找樹是一棵樹,對於樹,需要把節點表示出來。由於節點僅僅在樹的內部使用,故采用內部類的形式實現。
樹作為一種ADT,需要屬性及在其上進行的操作。由於大部分樹的操作都是從根節點開始的,因此需要一個根節點屬性,並可根據自己的需求來確定需要實現哪些操作。
對於二叉查找樹,它不是一般的二叉樹,它具有特點:任一節點的左子樹上的節點都比它小,右子樹上的節點都比它大。因此,二叉查找樹的方法實現需要滿足這個特點。
一,樹由結點組成
結點的定義如下:
1 private static class BinaryNode<T>{ 2 T element; 3 BinaryNode left; 4 BinaryNode right; 5 6 public BinaryNode(T element) { 7 this(element, null, null); 8 } 9 10 public BinaryNode(T element, BinaryNode<T>left, BinaryNode<T>right) { 11 this.element = element; 12 this.left = left; 13 this.right = right; 14 } 15 }
二,二叉查找樹 類
對於樹,需要有根結點。BinaryNode<T>以靜態內部類方式聲明在BinarySearchTree<T>中。靜態內部類的功能就是:靜態內部類的對象的創建不需要依賴其外部類的對象。
BinarySearchTree采用默認構造方法創建對象,然后調用 insert 方法向樹中添加節點。
public class BinarySearchTree<T extends Comparable<? super T>> { ......//BinaryNode<T>的聲明 private BinaryNode<T> root; ....... // 二叉查找樹中的方法 }
三,二叉查找樹的一些方法的遞歸實現及分析
1) insert(T ele),向二叉查找樹中插入一個元素。插入元素之后,返回樹根節點。
正確版本:
1 private BinaryNode<T> insert(T ele, BinaryNode<T> root){ 2 if(root == null) 3 return new BinaryNode<T>(ele); 4 int compareResult = ele.compareTo(root.element); 5 if(compareResult > 0) 6 root.right = insert(ele, root.right); 7 else if(compareResult < 0) 8 root.left = insert(ele, root.left); 9 else 10 ; 11 return root; 12 }
錯誤版本一:
1 private BinaryNode<T> insert1(T ele, BinaryNode<T> root){ 2 if(root == null) 3 return new BinaryNode<T>(ele); 4 5 int compareResult = ele.compareTo(root.element); 6 if(compareResult > 0) 7 return insert(ele, root.right); 8 else if(compareResult < 0) 9 return insert(ele, root.left); 10 else 11 return root; 12 13 }
這種版本的遞歸,返回的是:最后一層遞歸調用時,root所指向的節點。最后一層遞歸調用發生在葉子節點上,故返回的root是最后一個插入的元素。
判斷遞歸調用每一層的返回值時,可按照“壓棧”的順序進行分析。
如,上面程序當運行到第7行時,執行遞歸調用,那么會將當前的環境壓入棧中,保存起來。然后執行下一層的方法調用。
當下面的遞歸調用結束后,程序返回到第7行。繼續向后執行,可以看出第8行的 else if 和 第10行的 else的條件都不成立(因為程序已經在第7行條件成立了)
錯誤版本二:
1 private BinaryNode<T> insert2(T ele, BinaryNode<T> root){ 2 if(root == null) 3 return new BinaryNode<T>(ele); 4 int compareResult = ele.compareTo(root.element); 5 if(compareResult > 0) 6 root = insert2(ele, root.right); 7 else if(compareResult < 0) 8 root = insert2(ele, root.left); 9 else 10 ; 11 return root; 12 }
版本二的錯誤和版本一一樣,都沒有建立起父節點與左右孩子節點的連接。因此,最終得到的樹根節點指向的是最后一個插入的元素。
2)查找二叉樹中元素最大的節點
正確版本:
1 /* 2 * 關於尾遞歸的返回值,該方法只會返回二個值: null 和 'root' 3 * root 是最后一層遞歸調用時findMax的 root 參數 4 */ 5 private BinaryNode<T> findMax(BinaryNode<T> root){ 6 if(root == null) 7 return null; 8 if(root.right == null) 9 return root; 10 else 11 return findMax(root.right); 12 }
只有當樹為空時,才返回null。第一個if判斷才會執行,否則第一個if永遠不會執行。每一次遞歸都會使問題的規模縮小---從以root為根的樹,縮小成以root的右孩子為根的樹。
錯誤版本:
1 private BinaryNode<T> findMax1(BinaryNode<T> root){ 2 if(root == null) 3 return null; 4 else 5 return findMax(root.right); 6 } 7
該findMax遞歸版本,不管root是否為空,不管樹中有多少個節點,返回的值都為null。從中可以看出,這種形式的遞歸返回值,由其基准條件來決定。
3)先序遍歷的遞歸算法分析
1 public void preOrder(BinaryNode<T> root){ 2 if(root == null) 3 return; 4 System.out.print(root.element + " "); 5 preOrder(root.left); 6 preOrder(root.right); 7 }
該方法能夠清晰地分析遞歸的步驟。假設先序遍歷為e d c f g
e
d f
c g
1)對於節點e,執行到第4行,輸出e。在第5行,遞歸調用preOrder(d),並把preOrder(e)的相關信息壓入棧中保存。
2)對於節點d,執行到第4行,輸出d。在第5行,遞歸調用preOrder(c),並把preOrder(d)的相關信息壓入棧中保存。
3)對於節點c,執行到第4行,輸出c。在第5行,遞歸調用preOrder(c.left ‘null’),執行到第三行return null.
此時,返回到 2)由於preOrder(d)在棧頂,彈出preOrder(d)的信息。
由於preOrder(d)在它的下一層遞歸前執行到了第5行,故它從第6行繼續向前執行:preOrder(d.right)=preOrder(null)。並又把preOrder(d)的相關信息壓入棧中。
4)執行preOrder(d.right==null)從第3行 return null
此時,又彈出preOrder(d),此時從第6行向下繼續執行,遇到了右大括號,執行結束,返回到上層遞歸處.
5)此時preOrder(e)在棧頂,彈出。從第5行斷點處繼續向下執行:preOrder(e.right)=preOrder(f)
6) 對於節點f,執行到第4行,輸出f。在第5行,遞歸調用preOrder(f.left)=preOrder(null),並把preOrder(f)的相關信息壓入棧保存。
7)執行preOrder(f.left)在第三行返回,彈出preOrder(f)
8) 從preOrder(f)的斷點第5行處向下執行到第6行:preOrder(f.right)=preOrder(g)
9)對於節點g,執行到第4行,輸出g。在第5行遞歸調用preOrder(g.left),並將preOrder(g)的相關信息入棧....
.....
.....
二叉查找樹完整實現代碼如下:
1 package c4; 2 3 import java.util.Random; 4 5 import c2.C2_2_8; 6 7 public class BinarySearchTree<T extends Comparable<? super T>> { 8 9 private static class BinaryNode<T> { 10 T element; 11 BinaryNode<T> left; 12 BinaryNode<T> right; 13 14 public BinaryNode(T element) { 15 this(element, null, null); 16 } 17 18 public BinaryNode(T element, BinaryNode<T> left, BinaryNode<T> right) { 19 this.element = element; 20 this.left = left; 21 this.right = right; 22 } 23 24 public String toString() { 25 return element.toString(); 26 } 27 } 28 29 private BinaryNode<T> root; 30 31 public BinarySearchTree() { 32 root = null; 33 } 34 35 public void makeEmpty() { 36 root = null; 37 } 38 39 public boolean isEmpty() { 40 return root == null; 41 } 42 43 public boolean contains(T ele) { 44 return contains(ele, root); 45 } 46 47 private boolean contains(T ele, BinaryNode<T> root) { 48 if (root == null) 49 return false; 50 int compareResult = ele.compareTo(root.element); 51 if (compareResult > 0) 52 return contains(ele, root.right); 53 else if (compareResult < 0) 54 return contains(ele, root.left); 55 else 56 return true; 57 } 58 59 public BinaryNode<T> findMax() { 60 return findMax(root); 61 } 62 63 /* 64 * 關於尾遞歸的返回值,該方法只會返回二個值: null 和 'root' root 是最后一層遞歸調用時findMax的 root 參數 65 */ 66 private BinaryNode<T> findMax(BinaryNode<T> root) { 67 if (root == null) 68 return null; 69 if (root.right == null) 70 return root; 71 else 72 return findMax(root.right); 73 } 74 75 public BinaryNode<T> findMin() { 76 return findMin(root); 77 } 78 79 private BinaryNode<T> findMin(BinaryNode<T> root) { 80 if (root == null) 81 return null; 82 if (root.left == null) 83 return root; 84 else 85 return findMin(root.left); 86 } 87 88 public void insert(T ele) { 89 root = insert(ele, root);// 每次插入操作都會'更新'根節點. 90 } 91 92 private BinaryNode<T> insert(T ele, BinaryNode<T> root) { 93 if (root == null) 94 return new BinaryNode<T>(ele); 95 int compareResult = ele.compareTo(root.element); 96 if (compareResult > 0) 97 root.right = insert(ele, root.right); 98 else if (compareResult < 0) 99 root.left = insert(ele, root.left); 100 else 101 ; 102 return root; 103 } 104 105 public void preOrder(BinaryNode<T> root) { 106 if (root == null) 107 return; 108 System.out.print(root.element + " "); 109 preOrder(root.left); 110 preOrder(root.right); 111 } 112 113 public void inOrder(BinaryNode<T> root) { 114 if (root == null) 115 return; 116 inOrder(root.left); 117 System.out.print(root.element + " "); 118 inOrder(root.right); 119 } 120 121 public int height() { 122 return height(root); 123 } 124 125 private int height(BinaryNode<T> root) { 126 if (root == null) 127 return -1;// 葉子節點的高度為0,空樹的高度為1 128 129 return 1 + (int) Math.max(height(root.left), height(root.right)); 130 } 131 132 public static void main(String[] args) { 133 BinarySearchTree<String> tree = new BinarySearchTree<>(); 134 tree.insert("e"); 135 tree.insert("d"); 136 tree.insert("c"); 137 tree.insert("f"); 138 tree.insert("g"); 139 140 System.out.println("contains g? " + tree.contains("g")); 141 System.out.println("contains h? " + tree.contains("h")); 142 System.out.println("max node: " + tree.findMax().toString()); 143 tree.preOrder(tree.root); 144 System.out.println(); 145 tree.inOrder(tree.root); 146 System.out.println("\nheight: " + tree.height()); 147 } 148 }