1、數據結構java
二叉查找樹基於二叉樹,每一個節點儲存着鍵和值,以及指向左右子樹的連接,一顆二叉查找樹表明了一組鍵值對的集合,相似於python中的字典(字典中的鍵值對儲存是無序的)。在這裏咱們規定節點左子樹中的節點的鍵都小於它,右子樹中的節點都大於它,若是咱們將全部節點向下投影到一條線上,能夠獲得一條有序的序列。python
2、主要方法(默認爲遞歸實現)算法
部分方法的迭代實如今末尾展現數據結構
一、查找和排序ide
在符號表中查找一個鍵,從根節點開始,若是節點的鍵和被查找的鍵相等,就返回節點的值,若是節點的值大於被查找的鍵的值,就在節點的左子樹中查找,反之在右子樹中查找,直到節點爲空或查找命中。若是鍵在符號表中則查找命中,返回鍵對應的值,若是鍵不在符號表中則未命中,返回null(null爲指向一個它可能會存在的空節點的連接)函數
在上一篇結尾處引入的方法有許多都用到了這個算法:測試
1 public Value get(Key key) {// 經過鍵從符號表中獲取對應的值,返回鍵key對應的值
2 return get(root, key); 3 } 4
5 private Value get(Node x, Key key) {// 遞歸
6 if (key == null) { 7 return null; 8 } 9 int cmp = key.compareTo(x.key); 10 if (cmp < 0) { 11 return get(x.left, key); 12 } else if (cmp > 0) { 13 return get(x.right, key); 14 } else
15 return x.val; 16 } 17
18
19 public void put(Key key, Value val) {//修改或添加節點 20 root = put(root, key, val); 21 } 22
23 private Node put(Node x, Key key, Value val) { 24 if (x == null) {// 建立新節點
25 return new Node(key, val, 1); 26 } 27 int cmp = key.compareTo(x.key); 28 if (cmp < 0) { 29 x.left = put(x.left, key, val); 30 } else if (cmp > 0) { 31 x.right = put(x.right, key, val); 32 } else { 33 x.val = val; 34 } 35 x.N = size(x.left) + size(x.right) + 1; 36 return x;// 在插入結束時返回(更新節點的相關信息)
37 }
其中的size()方法表示以節點爲根節點的子樹的節點數:this
1 public int size() { 2 return size(root); 3 } 4
5 private int size(Node x) { 6 if (x != null) { 7 return x.N; 8 } else { 9 return 0; 10 } 11 }
size()的遞歸實現須要理解其運行細節spa
因爲是遞歸實現,代碼第35將會從查找通過的路徑由下至上,更新每一個節點計數器的值。code
在最壞狀況下,一顆有n個節點的二叉樹到根節點的距離爲n,最好狀況下,每條空連接和根節點的距離都爲lgN。你們能夠想象一下這兩種狀況下樹的樣子。
下面請讀者思考用遞歸的形式完成查找最小鍵和在樹中查找小於等於key的最大鍵兩個方法。
1 public Key min() { 2 return min(root).key; 3 } 4 public Node min(Node x) { 5 if(x.left==null) { 6 return x; 7 }else { 8 return min(x.left); 9 } 10 }
1 public Key floor(Key key) { 2 Node x== floor(root,key); 3 if(x==null) { 4 return null; 5 }else { 6 return x.key; 7 } 8 } 9
10 public Node floor(Node x,key) { 11 if (x == null) { 12 return null; 13 } 14 int cmp=key.compareto(x.key); 15 if(cmp<0) { 16 return floor(x.left,key); 17 } 18 Node t=floor(x.right,key); 19 if(t!=null) { 20 return t; 21 }else { 22 return x; 23 } 24 }
與之相反的max和ceiling(返回大於等於key的最小鍵)方法,實現原理相同。請讀者當作練習實現,參考最後的代碼展現。
二、排名
下面咱們將要實現兩個重要的方法,rank和select,select方法的參數是一個整型,他將返回排名爲這個整型的鍵,而rank和select相反,參數爲一個鍵,返回的是這個鍵的排名。
select和rank是在size的基礎上實現的。
1 public Key select(int k) {// 返回排名爲k的鍵
2 return select(root, k).key; 3 } 4
5 private Node select(Node x, int k) {// 返回排名爲k的節點
6 if (x == null) { 7 return null; 8 } 9 int t = size(x.left); 10 if (t > k) { 11 return select(x.left, k); 12 } else if (t < k) { 13 return select(x.right, k - t - 1); 14 } else { 15 return x; 16 } 17 }
1 public int rank(Key key) {// 返回key的排名
2 return rank(key, root); 3 } 4
5 private int rank(Key key, Node x) { 6 if (key == null) { 7 return 0; 8 } 9 int cmp = key.compareTo(x.key); 10 if (cmp < 0) { 11 return rank(key, x.left); 12 } else if (cmp > 0) { 13 return rank(key, x.right) + size(x.left) + 1;//注意體會這個+1 14 } else { 15 return size(x.left); 16 } 17 }
rank和put有點像,都是先不斷比較肯定鍵的位置,不過rank返回的是下一次的遞歸加上已經肯定的排名。
三、刪除
deleteMin爲刪除最小節點,任何狀況下,最小節點只可能有右子樹,因此在執行操做時只需將被最小節點的右節點代替最小節點便可。(刪除最大節點與此相似)
1 public void deleteMin() { 2 root = deleteMin(root); 3 } 4
5 private Node deleteMin(Node x) { 6 if (x.left == null) { 7 return x.right; 8 } 9 x.left = deleteMin(x.left); 10 x.N = size(x.left) + size(x.right) + 1;//因爲刪除操做,因此須要更新路徑上節點的計數器 11 return x;// 在刪除結束後返回節點的相關信息
12 }
請體會這裏第十、11行代碼,理解運行細節
分析:返回和傳入相同,例如deleteMin(x),最後返回的將是x節點,但此時節點包含的信息已經改變,由於要更新x.N,實際運行會從被刪除節點的上一個節點開始,由下至上進行更新,須要肯定的是,刪除和返回操做都是在同一條路徑上進行的,沒通過的路徑將保持不變。
若是被刪除的節點左右節點都存在,操做將變得複雜,這裏給出一種經典的方案(但還不是最好)。
找到要刪除的節點,接着用節點右子樹的最小節點代替它,最後給新節點接上左右連接。
在這裏咱們會用到min方法來找到右子樹的最小節點,再用deleteMin方法來刪除右子樹中的最小節點,同時把deleteMin方法的返回值傳給新節點的右連接。
結合圖中流程和代碼更容易理解
1 public void delete(Key key) { 2 root = delete(root, key); 3 } 4
5 private Node delete(Node x, Key key) { 6 if (x == null) { 7 return null; 8 } 9 int cmp = key.compareTo(x.key); 10 if (cmp > 0) { 11 x.right = delete(x.right, key); 12 } else if (cmp < 0) { 13 x.left = delete(x.left, key); 14 } else {// 找到key
15 if (x.right == null) {// 無右子樹 將左子樹接上
16 return x.left; 17 } 18 if (x.left == null) { 19 return x.right; 20 } 21 Node t = x; 22 // **********************************************
23 x = min(t.right); // *被刪除節點有左右子樹,用右子樹中的最小節點代替它
24 x.left = t.left; // *替換後,左子樹保持不變
25 x.right = deleteMin(t.right); // *右子樹刪除最小節點後再接入 26 // **********************************************
27 } 28 x.N = size(x.left) + size(x.right) + 1;// 更新新節點和上一個節點的n
29 return x;// 不必定會用到
30 }
四、範圍查找
範圍查找即返回指定範圍內的全部鍵,這須要咱們遍歷二叉樹,在這裏咱們使用中序遍歷,將會獲得一組由小到大的序列。
在這以前先來看看怎麼怎麼將二叉樹查找樹中的全部鍵由小到大打印出來:
1 private void print(Node x) {//x爲樹的根節點
2 if(x==null) { 3 return; 4 } 5 print(x.left); 6 System.out.println(x.key); 7 print(x.right);; 8 }
在範圍查找中咱們將傳入兩個參數lo和hi來肯定範圍,並將範圍內的鍵存入隊列,最後返回隊列。
1 public Iterable<Key> keys() {// 返回查找二叉樹中的全部鍵
2 return keys(min(), max()); 3 } 4 public Iterable<Key> keys(Key lo, Key hi) {// 二叉樹的範圍查找操做
5 Queue<Key> queue = new Queue<Key>(); 6 keys(root, queue, lo, hi); 7 return queue; 8 } 9
10 private void keys(Node x, Queue<Key> queue, Key lo, Key hi) { 11 if (x == null) { 12 return; 13 } 14 int cmplo = lo.compareTo(x.key); 15 int cmphi = hi.compareTo(x.key); 16 // 三個if相似於中序遍歷
17 if (cmplo < 0) { 18 keys(x.left, queue, lo, hi); 19 } 20 if (cmplo <= 0 && cmphi >= 0) { 21 queue.enqueue(x.key); 22 } 23 if (cmphi > 0) { 24 keys(x.right, queue, lo, hi); 25 } 26 }
對於遞歸和非遞歸,有人證明在通常狀況下非遞歸的效率更高,若是樹不是平衡的,函數調用棧的深度可能會出現問題。遞歸形式能讓人更容易理解,經過實現再非遞歸理解代碼的本質。
3、代碼展現(包含全部代碼及非遞歸實現)
1 package Unit3; 2 3 import java.util.Stack; 4 5 import edu.princeton.cs.algs4.Queue; 6 7 public class BST<Key extends Comparable<Key>, Value> {// 二叉查找樹 8 private Node root;// 查找二叉樹的根節點 9 10 private class Node { 11 private Key key;// 鍵 12 private Value val;// 值 13 private Node left, right;// 指向子樹的連接 14 private int N;// 以該節點爲根節點的子樹中的總節點數 15 16 public Node(Key key, Value val, int N) { 17 this.key = key; 18 this.val = val; 19 this.N = N; 20 } 21 } 22 23 public int size() { 24 return size(root); 25 } 26 27 private int size(Node x) { 28 if (x != null) { 29 return x.N; 30 } else { 31 return 0; 32 } 33 } 34 35 public Value get(Key key) {// 返回鍵key對應的值 36 return get(root, key); 37 } 38 39 private Value get(Node x, Key key) {// 遞歸 40 if (key == null) { 41 return null; 42 } 43 int cmp = key.compareTo(x.key); 44 if (cmp < 0) { 45 return get(x.left, key); 46 } else if (cmp > 0) { 47 return get(x.right, key); 48 } else 49 return x.val; 50 } 51 52 /* 53 * private Value getTwo(Node x, Key key) {// 非遞歸 if (key == null) { return null; 54 * } int cmp; while (x != null) { cmp = key.compareTo(x.key); if (cmp < 0) { x = 55 * x.left; } else if (cmp > 0) { x = x.right; } else return x.val; } return 56 * null; } 57 */ 58 59 public void put(Key key, Value val) { 60 root = put(root, key, val); 61 } 62 63 private Node put(Node x, Key key, Value val) { 64 if (x == null) {// 建立新節點 65 return new Node(key, val, 1); 66 } 67 int cmp = key.compareTo(x.key); 68 if (cmp < 0) { 69 x.left = put(x.left, key, val); 70 } else if (cmp > 0) { 71 x.right = put(x.right, key, val); 72 } else { 73 x.val = val; 74 } 75 x.N = size(x.left) + size(x.right) + 1; 76 return x;// 在插入結束時返回(更新節點的相關信息) 77 } 78 79 private Node putTwo(Node x, Key key, Value val) {// 在存在基本操做基礎上的非遞歸put() 80 if (get(key) != null) { 81 select(root, rank(key)).val = val; 82 } 83 return x; 84 } 85 86 public Key min() { 87 return min(root).key; 88 } 89 90 private Node min(Node x) { 91 if (x.left == null) { 92 return x; 93 } 94 return min(x.left); 95 } 96 97 public Key max() { 98 return max(root).key; 99 } 100 101 private Node max(Node x) { 102 if (x.right == null) { 103 return x; 104 } 105 return min(x.right); 106 } 107 108 public Key floor(Key key) { 109 Node x = floor(root, key); 110 if (x == null) { 111 return null; 112 } 113 return x.key; 114 } 115 116 // private Node floor(Node x, Key key) {//測試(我的思路 117 // if(x==null) { 118 // return null; 119 // } 120 // int cmp=key.compareTo(x.key); 121 // if(cmp<0) { 122 // return floor(x.left,key); 123 // }else if(cmp>0) { 124 // if(x.right==null) { 125 // return x.right; 126 // } 127 // return floor(x.right,key); 128 // } 129 // else { 130 // return x; 131 // } 132 // } 133 134 private Node floor(Node x, Key key) {// 相似前序遍歷 找到後再也不執行後續操做 135 if (x == null) { 136 return null; 137 } 138 int cmp = key.compareTo(x.key); 139 if (cmp == 0) { 140 return x; 141 } 142 if (cmp < 0) { 143 return floor(x.left, key); 144 } 145 Node f = floor(x.right, key); 146 if (f != null) { 147 return f; 148 } else { 149 return x; 150 } 151 } 152 153 public Key ceiling(Key key) { 154 Node x = ceiling(root, key); 155 if (x == null) { 156 return null; 157 } 158 return x.key; 159 } 160 161 private Node ceiling(Node x, Key key) { 162 if (x == null) { 163 return null; 164 } 165 int cmp = key.compareTo(x.key); 166 if (cmp == 0) { 167 return x; 168 } 169 if (cmp > 0) {// 往大找 170 return ceiling(x.right, key); 171 } 172 Node f = floor(x.left, key); 173 if (f != null) { 174 return f; 175 } else { 176 return x; 177 } 178 } 179 180 public Key FOC(Key key, String s) {// 合併兩個方法(自創 181 Node x = null; 182 if (s.equals("floor")) { 183 x = floor(root, key); 184 } else if (s.equals("ceiling")) { 185 x = ceiling(root, key); 186 } else { 187 System.out.println("輸入錯誤!將返回null"); 188 } 189 if (x == null) { 190 return null; 191 } 192 return x.key; 193 } 194 195 public Key select(int k) {// 返回排名爲k的鍵 196 return select(root, k).key; 197 } 198 199 public Node select(Node x, int k) {// 返回排名爲k的節點 200 if (x == null) { 201 return null; 202 } 203 int t = size(x.left); 204 if (t > k) { 205 return select(x.left, k); 206 } else if (t < k) { 207 return select(x.right, k - t - 1); 208 } else { 209 return x; 210 } 211 } 212 213 public Node selectTwo(Node x, int k) { 214 if (x == null) { 215 return null; 216 } 217 while (k != size(x.left)) { 218 if (size(x.left) > k) { 219 x = x.left; 220 } else { 221 x = x.right; 222 k = k - size(x.left) - 1; 223 } 224 } 225 return x; 226 } 227 228 public int rank(Key key) {// 返回key的排名 229 return rank(key, root); 230 } 231 232 private int rank(Key key, Node x) { 233 if (key == null) { 234 return 0; 235 } 236 int cmp = key.compareTo(x.key); 237 if (cmp < 0) { 238 return rank(key, x.left); 239 } else if (cmp > 0) { 240 return rank(key, x.right) + size(x.left) + 1; 241 } else { 242 return size(x.left); 243 } 244 } 245 246 public void deleteMin() { 247 root = deleteMin(root); 248 } 249 250 private Node deleteMin(Node x) { 251 if (x.left == null) { 252 return x.right; 253 } 254 x.left = deleteMin(x.left); 255 x.N = size(x.left) + size(x.right) + 1; 256 return x;// 在刪除結束後返回節點的相關信息 257 } 258 259 public void delete(Key key) { 260 root = delete(root, key); 261 } 262 263 private Node delete(Node x, Key key) { 264 if (x == null) { 265 return null; 266 } 267 int cmp = key.compareTo(x.key); 268 if (cmp > 0) { 269 x.right = delete(x.right, key); 270 } else if (cmp < 0) { 271 x.left = delete(x.left, key); 272 } else {// 找到key 273 if (x.right == null) {// 無右子樹 將左子樹接上 274 return x.left; 275 } 276 if (x.left == null) { 277 return x.right; 278 } 279 Node t = x; 280 // ********************************************** 281 x = min(t.right); // *被刪除節點有左右子樹,用右子樹中的最小節點代替它 282 x.left = t.left; // *替換後,左子樹保持不變 283 x.right = deleteMin(t.right); // *右子樹刪除最小節點後再接入 284 // ********************************************** 285 } 286 x.N = size(x.left) + size(x.right) + 1;// 更新新節點和上一個節點的n 287 return x;// 不必定會用到 288 } 289 290 private void print(Node x) {//x爲樹的根節點 291 if(x==null) { 292 return; 293 } 294 print(x.left); 295 System.out.println(x.key); 296 print(x.right);; 297 } 298 299 public Iterable<Key> keys() {// 返回查找二叉樹中的全部鍵 300 return keys(min(), max()); 301 } 302 303 public Iterable<Key> keysTwo() {// 範圍查找非遞歸方法 304 Stack<Node> stack = new Stack(); 305 Queue<Key> queue = new Queue<Key>(); 306 Node x = root; 307 while (x != null || !stack.isEmpty()) {// 中序遍歷非遞減 308 if (x != null) { 309 stack.add(x); 310 x = x.left; 311 } else { 312 x = stack.pop(); 313 queue.enqueue(x.key); 314 x = x.right; 315 } 316 } 317 /* 318 * while(x!=null||stack.isEmpty()) {// 中序遍歷非遞增 if(x!=null) { stack.add(x); 319 * x=x.right; }else { x=stack.pop(); queue.enqueue(x.key); x=x.left; } } 320 */ 321 return queue;// 返回非降序隊列 322 } 323 324 public Iterable<Key> keysThree(int i) {// 範圍查找非遞歸方法(包含前中序遍歷) 325 Stack<Node> stack = new Stack(); 326 Queue<Key> queue1 = new Queue<Key>();// 保存前序遍歷 327 Queue<Key> queue2 = new Queue<Key>();// 保存中序遍歷 328 Node x = root; 329 while (x != null || !stack.isEmpty()) { 330 for (; x != null; x = x.left) { 331 stack.add(x); 332 queue1.enqueue(x.key);// 前序遍歷 333 } 334 for (; x == null && !stack.isEmpty(); x = x.right) { 335 x = stack.pop(); 336 queue2.enqueue(x.key);// 中序遍歷 337 } 338 } 339 if (i == 1) { 340 return queue1; 341 } 342 return queue2; 343 } 344 345 public Iterable<Key> keysFour() {// 範圍查找非遞歸方法(後序遍歷) 346 Stack<Node> stackA = new Stack(); 347 Stack<Node> stackB = new Stack(); 348 Queue<Key> queue = new Queue<Key>();// 保存後序遍歷 349 Node x = root; 350 while (x != null || !stackB.isEmpty()) { 351 for (; x != null; x = x.right) { 352 stackA.add(x); 353 stackB.add(x); 354 } 355 for (; x == null && !stackB.isEmpty(); x = x.left) { 356 x = stackB.pop(); 357 } 358 } 359 360 while (!stackA.isEmpty()) { 361 queue.enqueue(stackA.pop().key); 362 } 363 return queue; 364 } 365 366 public Iterable<Key> keys(Key lo, Key hi) {// 二叉樹的範圍查找操做 367 Queue<Key> queue = new Queue<Key>(); 368 keys(root, queue, lo, hi); 369 return queue; 370 } 371 372 private void keys(Node x, Queue<Key> queue, Key lo, Key hi) { 373 if (x == null) { 374 return; 375 } 376 int cmplo = lo.compareTo(x.key); 377 int cmphi = hi.compareTo(x.key); 378 // 三個if相似於中序遍歷 379 if (cmplo < 0) { 380 keys(x.left, queue, lo, hi); 381 } 382 if (cmplo <= 0 && cmphi >= 0) { 383 queue.enqueue(x.key); 384 } 385 if (cmphi > 0) { 386 keys(x.right, queue, lo, hi); 387 } 388 } 389 390 public static void main(String[] args) { 391 BST<Integer, String> bst = new BST<Integer, String>(); 392 bst.put(5, "one"); 393 bst.put(4, "two"); 394 bst.put(2, "one"); 395 bst.put(6, "two"); 396 bst.put(3, "one"); 397 for (Integer x : bst.keysFour()) {// 中序遍歷 398 System.out.println(x + " " + bst.get(x)); 399 } 400 // bst.delete(2); 401 } 402 }