查找算法之——二叉查找樹(圖文分析)

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 }
View Code
相關文章
相關標籤/搜索