【從蛋殼到滿天飛】JAVA 數據結構解析和算法實現-二分搜索樹

思惟導圖

前言

【從蛋殼到滿天飛】JAVA 數據結構解析和算法實現,所有文章大概的內容以下:
Arrays(數組)、Stacks(棧)、Queues(隊列)、LinkedList(鏈表)、Recursion(遞歸思想)、BinarySearchTree(二分搜索樹)、Set(集合)、Map(映射)、Heap(堆)、PriorityQueue(優先隊列)、SegmentTree(線段樹)、Trie(字典樹)、UnionFind(並查集)、AVLTree(AVL 平衡樹)、RedBlackTree(紅黑平衡樹)、HashTable(哈希表)php

源代碼有三個:ES6(單個單個的 class 類型的 js 文件) | JS + HTML(一個 js 配合一個 html)| JAVA (一個一個的工程)html

所有源代碼已上傳 github,點擊我吧,光看文章可以掌握兩成,動手敲代碼、動腦思考、畫圖才能夠掌握八成。前端

本文章適合 對數據結構想了解而且感興趣的人羣,文章風格一如既往如此,就以爲手機上看起來比較方便,這樣顯得比較有條理,整理這些筆記加源碼,時間跨度也算將近半年時間了,但願對想學習數據結構的人或者正在學習數據結構的人羣有幫助。java

樹結構

  1. 線性數據結構是把全部的數據排成一排node

    1. 樹結構是倒立的樹,由一個根節點延伸出不少新的分支節點。
  2. 樹結構自己是一個種自然的組織結構python

    1. 如 電腦中文件夾目錄結構就是樹結構
    2. 這種結構來源於生活,
    3. 好比 圖書館總體分紅幾個大館,
    4. 如 數理館、文史館等等,
    5. 到了數理館還要分紅 不少的子類,
    6. 如 數學類的圖書、物理類的圖書、化學類的圖書,計算機類的圖書,
    7. 到了計算機類的圖書還要再分紅各類不一樣的子類,
    8. 如 按語言分類 c++、java、c#、php、python 等等,
    9. 如 按領域分類 網站編程、app 開發、遊戲開發、前端、後端等等,
    10. 每個子領域可能又要分紅不少領域,
    11. 一直到最後索引到一本一本的書,
    12. 這就是一個典型的樹結構。
    13. 還有 一個公司的組織架構也是這樣的一種樹結構,
    14. 從 CEO 開始下面可能有不一樣的部門,
    15. 如財務部門(Marketing Head)、人事部門(HR Head)、
    16. 技術部門(Finance Head)、市場部門(Audit Officer)等等,
    17. 每一個部門下面還有不一樣的職能分工,最後纔到具體的一個一我的。
    18. 還有家譜,他自己也是一個樹結構,
    19. 其實樹結構並不抽象,在生活中隨處可見。
  3. 樹結構很是的高效c++

    1. 好比文件管理,
    2. 不可能將全部的文件放到一個文件夾中,
    3. 而後用一個線性的結構進行存儲,
    4. 那樣的話查找文件太麻煩了,
    5. 可是若是給它作成樹機構的話,
    6. 那麼就能夠很容易的檢索到目標文件,
    7. 好比說我想檢索到個人照片,
    8. 直接找到我的文件夾,而後找到圖片文件夾,
    9. 最後找到本身的照片,這樣就很快速很高效的找到了目標文件。
    10. 在公司使用這種樹形的組織架構也是這個緣由,
    11. CEO 想就技術開發的一些問題進行一些討論,
    12. 他確定要找相應職能的一些人,
    13. 他不須要去市場部門、營銷部門、人事部門、財務部門、行政部門找人,
    14. 他直接去技術部這樣的開發部門去找人就行了,
    15. 一會兒就把查詢的範圍縮小了。
    16. 在數據結構領域設計樹結構的本質也是如此。
  4. 在計算機科學領域不少問題的處理git

    1. 當你將數據使用樹結構進行存儲後,出奇的高效。
  5. 二分搜索樹(Binary Search Tree)github

    1. 二分搜索樹有它的侷限性
  6. 平衡二叉樹:AVL;紅黑樹,面試

    1. 平衡二叉樹還有不少種
  7. 算法須要使用一些特殊的操做的時候將數據組織成樹結構

    1. 會針對某一類特殊的操做產生很是高效的結果,
    2. 使用以及並查集
    3. 都是爲了知足對數據某一個類特殊的操做進行高效的處理,
    4. 同時對於某些特殊的數據,不少時候能夠另闢蹊徑,
    5. 將他們以某種形式存儲成樹結構,
    6. 結果就是會對這類特殊的數據
    7. 它們所在的那個領域的問題
    8. 相應的解決方案提供極其高效的結果。
  8. 線段樹、Trie(字典樹、前綴樹)

    1. 線段樹主要用來處理線段這種特殊的數據,
    2. Trie 主要用於處理字符串這類特殊的數據,
    3. 要想實現快速搜索的算法,
    4. 它的本質依然是須要使用樹結構的,
    5. 樹結構不見得是顯式的展現在你面前,
    6. 它同時也能夠用來處理不少抽象的問題,
    7. 這就像棧的應用同樣,
    8. 從用戶的角度看只看撤銷這個操做或者只看括號匹配的操做,
    9. 用戶根本想不到這背後使用了一個棧的數據結構,
    10. 可是爲了組建出這樣的功能是須要使用這種數據結構的,
    11. 同理樹也是如此,不少看起來很是高效的運算結果,
    12. 它的背後實際上是由於有樹這種數據結構做爲支撐的,
    13. 這也是數據結構、包括數據結構在計算機科學領域很是重要的意義,
    14. 數據結構雖然解決的是數據存儲的問題,
    15. 可是在使用的層面上不只僅是由於要存儲數據,
    16. 更重要的是在你使用某些特殊的數據結構存儲數據後,
    17. 能夠幫助你輔助你更加高效的解決某些算法問題
    18. 甚至對於某些問題來講若是沒有這些數據結構,
    19. 那麼根本無從解決。

二分搜索樹(Binary Search Tree)

  1. 二叉樹

    1. 和鏈表同樣,也屬於動態數據結構,
    2. 不須要建立這個數據結構的時候就定好存儲的容量,
    3. 若是要添加元素,直接 new 一個新的空間,
    4. 而後把它添加到這個數據結構中,刪除也是同理,
    5. 每個元素也是存到一個節點中,
    6. 這個節點和鏈表不一樣,它除了要存放這個元素 e,
    7. 它還有兩個指向其它節點的變量,分別叫作 left、right,
    class Node {
          E e;
          Node left;
          Node right;
       }
  2. 二叉樹也叫多叉樹,

    1. 它每個節點最多隻能分紅兩個叉,
    2. 根據這個定義也能定義出多叉樹,
    3. 若是每一個節點能夠分出十個叉,
    4. 那就能夠叫它十叉樹,能分多少叉就叫多少叉樹,
    5. Trie 字典書自己就是一個多叉樹。
  3. 在數據結構領域對應樹結構來講

    1. 二叉樹是最經常使用的一種樹結構,
    2. 二叉樹具備一個惟一的根節點,
    3. 也就是最上面的節點。
    4. 每個節點最多有兩個子節點,
    5. 這兩個子節點分別叫作這個節點的左孩子和右孩子,
    6. 子節點指向左邊的那個節點就是左孩子,
    7. 子節點指向右邊的那個節點就是右孩子。
    8. 二叉樹每一個節點最多有兩個孩子,
    9. 一個孩子都沒有的節點一般稱之爲葉子節點,
    10. 二叉樹每一個節點最多有一個父親,
    11. 根節點是沒有父親節點的。
  4. 二叉樹和鏈表同樣具備自然遞歸的結構

    1. 鏈表自己是線性的,
    2. 它的操做既可使用循環也可使用遞歸。
    3. 和樹相關的不少操做,
    4. 使用遞歸的方式去寫要比使用非遞歸的方式簡單不少。
    5. 二叉樹每個節點的左孩子同時也是一個二叉樹的根節點,
    6. 一般叫管這棵二叉樹作左子樹。
    7. 二叉樹每個節點的右孩子同時也是一個二叉樹的根節點,
    8. 一般叫管這棵二叉樹作右子樹。
    9. 也就是說每個二叉樹它的左側和右側右分別鏈接了兩個二叉樹,
    10. 這兩個二叉樹都是節點個數更小的二叉樹,
    11. 這就是二叉樹所具備的自然的遞歸結構。
  5. 二叉樹不必定是「滿」的

    1. 滿二叉樹就是除了葉子節點以外,
    2. 每個節點都有兩個孩子。
    3. 就算你整個二叉樹上只有一個節點,
    4. 它也是一個二叉樹,只不過它的左右孩子都是空,
    5. 這棵二叉樹只有一個根節點,
    6. 甚至 NULL(空)也是一棵二叉樹。
    7. 就像鏈表中,只有一個節點它也是一個鏈表,
    8. 也能夠把 NULL(空)看做是一個鏈表。
  6. 二分搜索樹是一棵二叉樹

    1. 在二叉樹定義下全部其它的術語在二分搜索樹中也適用,
    2. 如 根節點、葉子節點、左孩子右孩子、左子樹、右子樹、
    3. 父親節點等等,這些在二分搜索樹中也同樣。
  7. 二分搜索樹的每個節點的值

    1. 都要大於其左子樹的全部節點的值,
    2. 都要小於其右子樹的全部節點的值。
    3. 在葉子節點上沒有左右孩子,
    4. 那就至關於也知足這個條件。
  8. 二分搜索樹的每一棵子樹也是二分搜索樹

    1. 對於每個節點來講,
    2. 它的左子樹全部的節點都比這個節點小,
    3. 它的右子樹全部的節點都比這個節點大,
    4. 那麼用二分搜索樹來存儲數據的話,
    5. 那麼再來查找一個數據就會變得很是簡單,
    6. 能夠很快的知道從左側找仍是右側找,
    7. 甚至能夠不用看另一側,
    8. 因此就大大的加快了查詢速度。
    9. 在生活中使用樹結構,本質也是如此,
    10. 例如我要找一本 java 編程的書,
    11. 那麼進入圖書館我直接進入計算機科學這個區域找這本書,
    12. 其它的類的圖書我根本不用去管,
    13. 這也是樹這種結構存儲數據以後再對數據進行操做時
    14. 纔可以很是高效的核心緣由。
  9. 爲了可以達到二分搜索樹的性質

    1. 必須讓存儲的元素具備可比較性,
    2. 你要定義好 元素之間如何進行比較,
    3. 由於比較的方式是具備多種的,
    4. 必須保證元素之間能夠進行比較。
    5. 在鏈表和數組中則沒有這個要求,
    6. 這個就是二分搜索樹存儲數據的一個侷限性,
    7. 也說明了凡事都是有代價的,
    8. 若是想加快搜索的話就必須對數據有必定的要求。

代碼示例

  1. 二分搜索樹其實不是支持全部的類型

    1. 因此應該對元素的類型有所限制,
    2. 這個限制就是 這個類型必須擁有可比較性,
    3. 因此在 java 裏面的表示就是 對泛型進行約束,
    4. 泛型 E 必須知足 Comparable<E>,
    5. 也就是這個類型 E 必須具備可比較性。
  2. 代碼實現

    public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
       }

向二分搜索樹中添加元素

  1. 若是二分搜索樹的根節點爲空的話

    1. 第一個添加的元素就會成爲根節點,
    2. 若是再添加一個元素,那麼就因該從根節點出發,
    3. 根據二分搜索樹的定義,
    4. 每一個節點的值要比它的左子樹上全部節點的值大,
    5. 假設第二個添加的元素的值小於第一個添加的元素的值,
    6. 那麼很顯然第二個添加的元素要被添加到根節點的左子樹上去,
    7. 根節點的左子樹上只有一個節點,
    8. 那麼這個節點就是左子樹上的根節點,
    9. 這個左子樹上的根節點就是頂層根節點的左孩子。
  2. 按照這樣的規則,每來一個新元素從根節點開始,

    1. 若是小於根節點,那麼就插入到根節點的左子樹上去,
    2. 若是大於根節點,那麼就插入到根節點的右子樹上去,
    3. 因爲無論是左子樹仍是右子樹,它們又是一棵二分搜索樹,
    4. 那麼這個過程就是依此類推下去,
    5. 一層一層向下比較新添加的節點的值,
    6. 大的向右,小的向左,不停的向下比較,
    7. 若是這個位置沒有被佔住,那麼就能夠在這個位置上添加進去,
    8. 若是這個位置被佔了,那就不停的向下比較,
    9. 直到找到一個合適的位置添加進去。
  3. 若是遇到兩個元素的值相同,那暫時先不去管,

    1. 也就是不添加進去,由於已經有了,
    2. 自定義二分搜索樹不包含重複元素,
    3. 若是想包含重複元素,
    4. 只須要定義左子樹小於等於節點、或者右子樹大於等於節點,
    5. 只要把「等於」這種關係放進定義裏就能夠了。
  4. 二分搜索樹添加元素的非遞歸寫法,和鏈表很像

    1. 可是在二分搜索樹方面的實現儘可能使用遞歸來實現,
    2. 就是要鍛鍊遞歸算法的書寫,
    3. 由於遞歸算法的不少細節和內容須要不斷去體會,
    4. 可是非遞歸的寫法也很實用的,
    5. 由於遞歸自己是具備更高的開銷的,
    6. 雖然在現代計算機上這些開銷並不明顯,
    7. 可是在一些極端的狀況下仍是能夠看出很大的區別,
    8. 尤爲是對於二分搜索樹來講,
    9. 在最壞的狀況下它有可能會退化成一個鏈表,
    10. 那麼在這種狀況下使用遞歸的方式很容易形成系統棧的溢出,
    11. 二分搜索樹一些非遞歸的實現你能夠本身練習一下。
  5. 在二分搜索樹方面,遞歸比非遞歸實現起來更加簡單。

代碼示例

  1. 代碼

    public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             public void add (E e) {
                   if (root == null) {
                         root = new Node(e);
                         size ++;
                   } else {
                         add(root, e);
                   }
             }
    
             // 向以node爲根的二分搜索樹種插入元素E,遞歸算法
             private void add (Node node, E e) {
                   // node 是對用戶屏蔽的,用戶不用知道二分搜索樹中有怎樣一個節點結構
    
                   // 若是出現相同的元素就不進行操做了
                   if (e.equals(node.e)) {
                         return;
                   } else if (e.compareTo(node.e) < 0 && node.left == null) {
                         // 給左孩子賦值
                         node.left = new Node(e);
                         size ++;
                         return;
                   } else if (e.compareTo(node.e) > 0 && node.right == null) {
                         // 給右海子賦值
                         node.right = new Node(e);
                         size ++;
                         return;
                   }
    
                   // 這裏是處理節點被佔了,那就進入下一個層的二叉樹中
                   if (e.compareTo(node.e) < 0) {
                         // 去左子樹
                         add(node.left, e);
                   } else { // e.compareTo(node.e) > 0
                         // 去右子樹
                         add(node.right, e);
                   }
             }
       }
  2. 對於二分搜索的插入操做

    1. 上面的代碼是相對比較複雜的,
    2. 能夠進行改進一下,
    3. 讓代碼總體簡潔一些,
    4. 由於遞歸算法是有不少不一樣的寫法的,
    5. 並且遞歸的終止條件也是有不一樣的考量。

深刻理解遞歸終止條件

  1. 改進添加操做

    1. 遞歸算法有不少不一樣的寫法,
    2. 遞歸的終止條件也有不一樣的考量。
  2. 以前的算法

    1. 向以 node 爲根的二分搜索樹中插入元素 e,
    2. 其實將新的元素插入至 node 的左孩子或者右孩子,
    3. 若是 node 的左或右孩子爲空,那能夠進行相應的賦值操做,
    4. 若是是 node 的左右孩子都不爲空的話,
    5. 那就只能遞歸的插入到相應 node 的左或右孩子中,
    6. 由於這一層節點已經滿了,只能考慮下一層了,
    7. 下一層符合要求而且節點沒有滿,就能夠進行相應的賦值操做了。
    8. 可是有對根節點作出了特殊的處理,要防止根節點爲空的狀況發生,
    9. 若是根節點爲空,那麼就將第一個元素賦值爲根節點,
    10. 可是除了根節點之外,其它節點不須要作這種特殊處理,
    11. 因此致使邏輯上並不統一,而且遞歸的終止條件很是的臃腫,

代碼示例

  1. 代碼

    public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             // 改進:直接調用add
             public void add (E e) {
                   root = add(root, e);
             }
    
             // 向以node爲根的二分搜索樹種插入元素E,遞歸算法
             // 改進:返回插入的新節點後二分搜索樹的根
             private Node add (Node node, E e) {
    
                   // 處理最基本的問題
                   if (node == null) {
                         size ++;
                         return new Node(e);
                   }
    
                   // 空的二叉樹也是叉樹。
                   if (e.compareTo(node.e) < 0) {
                         // 將處理後的結果賦值給node的左子樹
                         node.left =  add(node.left, e);
                   } else if (e.compareTo(node.e) > 0) {
                         // 將處理後的結果賦值給node的右子樹
                         node.right =  add(node.right, e);
                   } // 若是相同 就什麼都不作
    
                   // 最後返回這個node
                   return node;
             }
    
       //    // 向二分搜索樹中添加一個元素 e
       //    public void add (E e) {
       //        if (root == null) {
       //            root = new Node(e);
       //            size ++;
       //        } else {
       //            add(root, e);
       //        }
       //    }
    
       //    // 向以node爲根的二分搜索樹種插入元素E,遞歸算法
       //    private void add (Node node, E e) {
       //        // node 是對用戶屏蔽的,用戶不用知道二分搜索樹中有怎樣一個節點結構
       //
       //        // 若是出現相同的元素就不進行操做了
       //        if (e.equals(node.e)) {
       //            return;
       //        } else if (e.compareTo(node.e) < 0 && node.left == null) {
       //            // 給左孩子賦值
       //            node.left = new Node(e);
       //            return;
       //        } else if (e.compareTo(node.e) > 0 && node.right == null) {
       //            // 給右海子賦值
       //            node.right = new Node(e);
       //            return;
       //        }
       //
       //        // 這裏是處理節點被佔了,那就進入下一個層的二叉樹中
       //        if (e.compareTo(node.e) < 0) {
       //            // 去左子樹
       //            add(node.left, e);
       //        } else { // e.compareTo(node.e) > 0
       //            // 去右子樹
       //            add(node.right, e);
       //        }
       //    }
       }
  2. 雖然代碼量更少了,可是也更難理解的了一些

    1. 首先從宏觀的語意的角度去理解定義這個函數的語意後
    2. 整個遞歸函數處理的邏輯如何成立的,
    3. 其次從微觀的角度上能夠寫一些輔助代碼來幫助你一點一點的查看,
    4. 從一個空的二分搜索樹開始,往裏添加三五個元素,
    5. 看看每一個元素是如何逐步的添加進去。
    6. 能夠嘗試一些鏈表這個程序插入操做的遞歸算法,
    7. 其實這兩者之間是擁有很是高的類似度的,
    8. 只不過在二分搜索樹中須要判斷一下是須要插入到左子樹仍是右子樹而已,
    9. 對於鏈表來講直接插入到 next 就行了,
    10. 經過兩者的比較就能夠更加深刻的理解這個程序。

二分搜索樹的查詢操做

  1. 查詢操做很是的容易

    1. 只須要不停的看每個 node 裏面存的元素,
    2. 不會牽扯到整個二分搜索樹的添加操做
  2. 和添加元素同樣須要使用遞歸的進行實現

    1. 在遞歸的過程當中就須要從二分搜索樹的根開始,
    2. 逐漸的轉移在二分搜索樹的子樹中縮小問題的規模,
    3. 縮小查詢的樹的規模,直到找到這個元素 e 或者發現找不到這個元素 e。
  3. 在數組和鏈表中有索引這個概念,

    1. 可是在二分搜索樹中沒有索引這個概念。

代碼示例

  1. 代碼

    public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             // 改進:直接調用add
             public void add (E e) {
                   root = add(root, e);
             }
    
             // 向以node爲根的二分搜索樹中插入元素E,遞歸算法
             // 改進:返回插入的新節點後二分搜索樹的根
             private Node add (Node node, E e) {
    
                   // 處理最基本的問題
                   if (node == null) {
                         size ++;
                         return new Node(e);
                   }
    
                   // 空的二叉樹也是叉樹。
                   if (e.compareTo(node.e) < 0) {
                         // 將處理後的結果賦值給node的左子樹
                         node.left =  add(node.left, e);
                   } else if (e.compareTo(node.e) > 0) {
                         // 將處理後的結果賦值給node的右子樹
                         node.right =  add(node.right, e);
                   } // 若是相同 就什麼都不作
    
                   // 最後返回這個node
                   return node;
             }
    
             // 查詢二分搜索數中是否包含某個元素
             public boolean contains (E e) {
                   return contains(root, e);
             }
    
             // 向以node爲根的二分搜索樹 進行查找  遞歸算法
             public boolean contains (Node node, E e) {
    
                   // 解決最基本的問題 也就是遍歷完全部節點都沒有找到
                   if (node == null) {
                         return false;
                   }
    
                   // 若是 e 小於當前節點的e 則向左子樹進發
                   if (e.compareTo(node.e) < 0) {
                       return contains(node.left, e);
                   } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發
                       return contains(node.right, e);
                   } else { // 若是e 等於 當前節點 e 則直接返回true
                         return true;
                   }
             }
       }

二分搜索樹的遍歷-前序遍歷

  1. 遍歷操做就是把這個數據結構中全部的元素都訪問一遍

    1. 在二分搜索樹中就是把全部節點都訪問一遍,
  2. 訪問數據結構中存儲的全部元素是由於與業務相關,

    1. 例如 給全部的同窗加兩分,給全部的員工發補貼等等,
    2. 因爲你的數據結構是用來存儲數據的,
    3. 不只能夠查詢某些特定的數據,
    4. 還應該有相關的方式將全部的數據都進行訪問。
  3. 在線性結構下,遍歷是極其容易的

    1. 不管是數組仍是鏈表只要使用一下循環就行了,
    2. 可是這件事在樹結構下沒有那麼簡單,
    3. 可是也沒有那麼難:)。
  4. 在樹結構下遍歷操做並無那麼難

    1. 若是你對樹結構不熟悉,那麼可能就有點難,
    2. 可是若是你熟悉了樹結構,那麼並不是是那麼難的操做,
    3. 尤爲是你在掌握遞歸操做以後,遍歷樹就更加不難了。
  5. 對於遍歷操做,兩個子樹都要顧及

    1. 即要訪問左子樹中全部的節點又要訪問右子樹中全部的節點,
    2. 下面的代碼中的遍歷方式也稱爲二叉樹的前序遍歷,
    3. 先訪問這個節點,再訪問左右子樹,
    4. 訪問這個節點放在了訪問左右子樹的前面因此就叫前序遍歷。
    5. 要從宏觀與微觀的角度去理解這個代碼,
    6. 從宏觀的角度來看,
    7. 定義好了遍歷的這個語意後整個邏輯是怎麼組建的,
    8. 從微觀的角度來看,真正的有一個棵二叉樹的時候,
    9. 這個代碼是怎樣怎樣一行一行去執行的。
    10. 當你熟練的掌握遞歸的時候,
    11. 有的時候你能夠不用遵照 那種先寫遞歸終止的條件,
    12. 再寫遞歸組成的的邏輯 這樣的一個過程,如寫法二,
    13. 雖然什麼都不幹,可是也是 return 了,
    14. 和寫法一中寫的邏輯實際上是等價的,
    15. 也就是在遞歸終止條件這部分能夠靈活處理。
    16. 寫法一看起來邏輯比較清晰,遞歸終止在前,遞歸組成的邏輯在後。
    // 遍歷以node爲根的二分搜索樹 遞歸算法
    function traverse(node) {
       if (node === null) {
          return;
       }
    
       // ... 要作的事情
    
       // 訪問該節點 兩邊都要顧及
       // 訪問該節點的時候就去作該作的事情,
       // 如 給全部學生加兩分
       traverse(node.left);
       traverse(node.right);
    }
    
    // 寫法二 這種邏輯也是能夠的
    function traverse(node) {
       if (node !== null) {
          // ... 要作的事情
    
          // 訪問該節點 兩邊都要顧及
          // 訪問該節點的時候就去作該作的事情,
          // 如 給全部學生加兩分
          traverse(node.left);
          traverse(node.right);
       }
    }

代碼示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             // 改進:直接調用add
             public void add (E e) {
                   root = add(root, e);
             }
    
             // 向以node爲根的二分搜索樹中插入元素E,遞歸算法
             // 改進:返回插入的新節點後二分搜索樹的根
             private Node add (Node node, E e) {
    
                   // 處理最基本的問題
                   if (node == null) {
                         size ++;
                         return new Node(e);
                   }
    
                   // 空的二叉樹也是叉樹。
                   if (e.compareTo(node.e) < 0) {
                         // 將處理後的結果賦值給node的左子樹
                         node.left =  add(node.left, e);
                   } else if (e.compareTo(node.e) > 0) {
                         // 將處理後的結果賦值給node的右子樹
                         node.right =  add(node.right, e);
                   } // 若是相同 就什麼都不作
    
                   // 最後返回這個node
                   return node;
             }
    
             // 查詢二分搜索數中是否包含某個元素
             public boolean contains (E e) {
                   return contains(root, e);
             }
    
             // 向以node爲根的二分搜索樹 進行查找  遞歸算法
             public boolean contains (Node node, E e) {
    
                   // 解決最基本的問題 也就是遍歷完全部節點都沒有找到
                   if (node == null) {
                         return false;
                   }
    
                   // 若是 e 小於當前節點的e 則向左子樹進發
                   if (e.compareTo(node.e) < 0) {
                       return contains(node.left, e);
                   } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發
                       return contains(node.right, e);
                   } else { // 若是e 等於 當前節點 e 則直接返回true
                         return true;
                   }
             }
    
             // 二分搜索樹的前序遍歷
             public void preOrder () {
                   preOrder(root);
             }
    
             // 前序遍歷以node爲根的二分搜索樹 遞歸算法
             public void preOrder (Node node) {
                   if (node == null) {
                         return;
                   }
    
                   // 輸出
                   System.out.println(node.e);
    
                   preOrder(node.left);
                   preOrder(node.right);
    
       //        // 這種邏輯也是能夠的
       //        if (node != null) {
       //            // 輸出
       //            System.out.println(node.e);
       //
       //            preOrder(node.left);
       //            preOrder(node.right);
       //        }
             }
       }
  2. Main

    public class Main {
    
             public static void main(String[] args) {
    
                   MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>();
    
                   int [] nums = {5, 3, 6, 8, 4, 2};
                   for (int i = 0; i < nums.length ; i++) {
                         mbst.add(nums[i]);
                   }
    
                   /////////////////
                   //      5      //
                   //    /   \    //
                   //   3    6    //
                   //  / \    \   //
                   // 2  4     8  //
                   /////////////////
                   mbst.preOrder();
             }
       }

二分搜索樹的遍歷調試-前序遍歷

  1. 遍歷輸出二分搜索樹

    1. 能夠寫一個輔助函數自動遍歷全部節點生成字符串,
    2. 輔助函數叫作 generateBSTString,
    3. 這個函數的做用是,生成以 node 爲根節點,
    4. 深度爲 depth 的描述二叉樹的字符串,
    5. 這樣一來要新增一個輔助函數,
    6. 這個函數的做用是,根據遞歸深度生成字符串,
    7. 這個輔助函數叫作 generateDepthString。

代碼示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             // 改進:直接調用add
             public void add (E e) {
                   root = add(root, e);
             }
    
             // 向以node爲根的二分搜索樹中插入元素E,遞歸算法
             // 改進:返回插入的新節點後二分搜索樹的根
             private Node add (Node node, E e) {
    
                   // 處理最基本的問題
                   if (node == null) {
                         size ++;
                         return new Node(e);
                   }
    
                   // 空的二叉樹也是叉樹。
                   if (e.compareTo(node.e) < 0) {
                         // 將處理後的結果賦值給node的左子樹
                         node.left =  add(node.left, e);
                   } else if (e.compareTo(node.e) > 0) {
                         // 將處理後的結果賦值給node的右子樹
                         node.right =  add(node.right, e);
                   } // 若是相同 就什麼都不作
    
                   // 最後返回這個node
                   return node;
             }
    
             // 查詢二分搜索數中是否包含某個元素
             public boolean contains (E e) {
                   return contains(root, e);
             }
    
             // 向以node爲根的二分搜索樹 進行查找  遞歸算法
             public boolean contains (Node node, E e) {
    
                   // 解決最基本的問題 也就是遍歷完全部節點都沒有找到
                   if (node == null) {
                         return false;
                   }
    
                   // 若是 e 小於當前節點的e 則向左子樹進發
                   if (e.compareTo(node.e) < 0) {
                       return contains(node.left, e);
                   } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發
                       return contains(node.right, e);
                   } else { // 若是e 等於 當前節點 e 則直接返回true
                         return true;
                   }
             }
    
             // 二分搜索樹的前序遍歷
             public void preOrder () {
                   preOrder(root);
             }
    
             // 前序遍歷以node爲根的二分搜索樹 遞歸算法
             public void preOrder (Node node) {
                   if (node == null) {
                         return;
                   }
    
                   // 輸出
                   System.out.println(node.e);
    
                   preOrder(node.left);
                   preOrder(node.right);
    
       //        // 這種邏輯也是能夠的
       //        if (node != null) {
       //            // 輸出
       //            System.out.println(node.e);
       //
       //            preOrder(node.left);
       //            preOrder(node.right);
       //        }
             }
    
             @Override
             public String toString () {
                   StringBuilder sb = new StringBuilder();
                   generateBSTString(root, 0, sb);
                   return sb.toString();
             }
    
             // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串
             private void generateBSTString (Node node, int depath, StringBuilder sb) {
                   if (node == null) {
                         sb.append(generateDepthString(depath) + "null\n");
                         return;
                   }
    
                   sb.append(generateDepthString(depath) + node.e + "\n");
    
                   generateBSTString(node.left, depath + 1, sb);
                   generateBSTString(node.right, depath + 1, sb);
    
             }
    
             // 生成路徑字符串
             private String generateDepthString (int depth) {
                   StringBuilder sb = new StringBuilder();
                   for (int i = 0; i < depth; i++) {
                         sb.append("-- ");
                   }
                   return  sb.toString();
             }
       }
  2. Main

    public class Main {
    
             public static void main(String[] args) {
    
                   MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>();
    
                   int [] nums = {5, 3, 6, 8, 4, 2};
                   for (int i = 0; i < nums.length ; i++) {
                         mbst.add(nums[i]);
                   }
    
                   /////////////////
                   //      5      //
                   //    /   \    //
                   //   3    6    //
                   //  / \    \   //
                   // 2  4     8  //
                   /////////////////
                   mbst.preOrder();
    
                   System.out.println();
    
                   // 輸出 調試字符串
                   System.out.println(mbst.toString());
             }
       }

二分搜索樹的遍歷-中序、後序遍歷

  1. 前序遍歷

    1. 前序遍歷是最天然的一種遍歷方式,
    2. 同時也是最經常使用的一種遍歷方式,
    3. 若是沒有特殊狀況的話,
    4. 在大多數狀況下都會使用前序遍歷。
    5. 先訪問這個節點,
    6. 而後訪問這個節點的左子樹,
    7. 再訪問這個節點的右子樹,
    8. 整個過程循環往復。
    9. 前序遍歷的表示先訪問的這個節點。
    function preOrder(node) {
       if (node == null) return;
    
       // ... 要作的事情
       // 訪問該節點
    
       // 先一直往左,而後不斷返回上一層 再向左、終止,
       // 最後整個操做循環往復,直到所有終止。
       preOrder(node.left);
       preOrder(node.right);
    }
  2. 中序遍歷

    1. 先訪問左子樹,再訪問這個節點,
    2. 最後訪問右子樹,整個過程循環往復。
    3. 中序遍歷的表示先訪問左子樹,
    4. 而後再訪問這個節點,最後訪問右子樹,
    5. 訪問這個節點的操做放到了訪問左子樹和右子樹的中間。
    function inOrder(node) {
       if (node == null) return;
    
       inOrder(node.left);
    
       // ... 要作的事情
       // 訪問該節點
    
       inOrder(node.right);
    }
  3. 中序遍歷後輸出的結果是排序後的結果。

    1. 中序遍歷的結果是二分搜索樹中
    2. 存儲的全部的元素從小到大進行排序後的結果,
    3. 這是二分搜索樹一個很重要的一個性質。
    4. 二分搜索樹任何一個節點的左子樹上全部的節點值都比當前節點的小,
    5. 二分搜索樹任何一個節點的右子樹上全部的節點值都比當前節點的大,
    6. 每個節點的遍歷都是從左往本身再往右,
    7. 先遍歷這個節點的左子樹,先把比本身節點小的全部元素都遍歷了,
    8. 再遍歷這個節點,而後再遍歷比這個節點大的全部元素,這個過程是遞歸完成的,
    9. 以 小於、等於、大於的順序遍歷獲得的結果天然就是一個從小到大的排序的,
    10. 你也能夠 使用大於 等於 小於的順序遍歷,那樣結果就是從大到小排序了。
    11. 也正是由於這個緣由,二分搜索樹有的時候也叫作排序樹,
    12. 這是二分搜索樹額外的效能,
    13. 當你使用數組、鏈表時若是想讓你的元素是順序的話,
    14. 必須作額外的工做,不然沒有辦法保證一次遍歷獲得的元素都是順序排列的,
    15. 可是對於二分搜索樹來講,你只要聽從他的定義,
    16. 而後使用中序遍歷的方式遍歷整棵二分搜索樹就可以獲得順序排列的結果。
  4. 後序遍歷

    1. 先訪問左子樹,再訪問右子樹,
    2. 最後訪問這個節點,整個過程循環往復。
    3. 後序遍歷的表示先訪問左子樹,
    4. 而後再訪問右子樹,最後訪問這個節點,
    5. 訪問這個節點的操做放到了訪問左子樹和右子樹的後邊。
    function inOrder(node) {
       if (node == null) return;
    
       inOrder(node.left);
       inOrder(node.right);
       // ... 要作的事情
       // 訪問該節點
    }
  5. 二分搜索樹的前序遍歷和後序遍歷並不像中序遍歷那樣進行了排序

    1. 後續遍歷的應用場景是那些必須先處理完左子樹的全部節點,
    2. 而後再處理完右子樹的全部節點,最後再處理當前的節點,
    3. 也就是處理完這個節點的孩子節點以後再去處理當前這個節點。
    4. 一個典型的應用是在內存釋放方面,若是須要你手動的釋放內存,
    5. 那麼就須要先把這個節點的孩子節點全都釋放完而後再來釋放這個節點自己,
    6. 這種狀況使用二叉樹的後序遍歷的方式,
    7. 先處理左子樹、再處理右子樹、最後處理本身。
    8. 可是例如javac#這樣的語言都有垃圾回收機制,
    9. 因此不須要你對內存管理進行手動的控制,
    10. c++ 語言中須要手動的控制內存,
    11. 那麼在二分搜索樹內存釋放這方面就須要使用後序遍歷。
    12. 對於一些樹結構的問題,
    13. 不少時候也是須要先針對一個節點的孩子節點求解出答案,
    14. 最終再由這些答案組合成針對這個節點的答案,
    15. 樹形問題有分治算法、回溯算法、動態規劃算法等等。
  6. 二分搜索樹的前中後序遍歷

    1. 主要從程序的角度進行分析,
    2. 不少時候對一些問題的分析,若是直接給你一個樹結構,
    3. 而後你可以直接看出來對於這棵樹來講它的前中後序遍歷的結果是怎樣的,
    4. 那就能夠大大加快解決問題的速度,
    5. 同時這樣的一個問題也是和計算機相關的考試的題目,
    6. 對於這樣的一個問題的更加深刻的理解
    7. 也能夠幫助你理解二分搜索樹這種數據結構。

代碼示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             // 改進:直接調用add
             public void add (E e) {
                   root = add(root, e);
             }
    
             // 向以node爲根的二分搜索樹中插入元素E,遞歸算法
             // 改進:返回插入的新節點後二分搜索樹的根
             private Node add (Node node, E e) {
    
                   // 處理最基本的問題
                   if (node == null) {
                         size ++;
                         return new Node(e);
                   }
    
                   // 空的二叉樹也是叉樹。
                   if (e.compareTo(node.e) < 0) {
                         // 將處理後的結果賦值給node的左子樹
                         node.left =  add(node.left, e);
                   } else if (e.compareTo(node.e) > 0) {
                         // 將處理後的結果賦值給node的右子樹
                         node.right =  add(node.right, e);
                   } // 若是相同 就什麼都不作
    
                   // 最後返回這個node
                   return node;
             }
    
             // 查詢二分搜索數中是否包含某個元素
             public boolean contains (E e) {
                   return contains(root, e);
             }
    
             // 向以node爲根的二分搜索樹 進行查找  遞歸算法
             private boolean contains (Node node, E e) {
    
                   // 解決最基本的問題 也就是遍歷完全部節點都沒有找到
                   if (node == null) {
                         return false;
                   }
    
                   // 若是 e 小於當前節點的e 則向左子樹進發
                   if (e.compareTo(node.e) < 0) {
                       return contains(node.left, e);
                   } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發
                       return contains(node.right, e);
                   } else { // 若是e 等於 當前節點 e 則直接返回true
                         return true;
                   }
             }
    
             // 二分搜索樹的前序遍歷
             public void preOrder () {
                   preOrder(root);
             }
    
             // 前序遍歷以node爲根的二分搜索樹 遞歸算法
             private void preOrder (Node node) {
                   if (node == null) {
                         return;
                   }
    
                   // 輸出
                   System.out.println(node.e);
    
                   preOrder(node.left);
                   preOrder(node.right);
    
       //        // 這種邏輯也是能夠的
       //        if (node != null) {
       //            // 輸出
       //            System.out.println(node.e);
       //
       //            preOrder(node.left);
       //            preOrder(node.right);
       //        }
             }
    
             // 二分搜索樹的中序遍歷
             public void inOrder () {
                   inOrder(root);
             }
    
             // 中序遍歷以node爲根的二分搜索樹 遞歸算法
             private void inOrder (Node node) {
                   if (node == null) return;
    
                   inOrder(node.left);
                   System.out.println(node.e);
                   inOrder(node.right);
    
             }
    
             // 二分搜索樹的後序遍歷
             public void postOrder () {
                   postOrder(root);
             }
    
             // 後續遍歷以node爲根的二分搜索樹 遞歸算法
             private void postOrder (Node node) {
                   if (node == null) return;
    
                   postOrder(node.left);
                   postOrder(node.right);
                   System.out.println(node.e);
             }
    
             @Override
             public String toString () {
                   StringBuilder sb = new StringBuilder();
                   generateBSTString(root, 0, sb);
                   return sb.toString();
             }
    
             // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串
             private void generateBSTString (Node node, int depath, StringBuilder sb) {
                   if (node == null) {
                         sb.append(generateDepthString(depath) + "null\n");
                         return;
                   }
    
                   sb.append(generateDepthString(depath) + node.e + "\n");
    
                   generateBSTString(node.left, depath + 1, sb);
                   generateBSTString(node.right, depath + 1, sb);
    
             }
    
             // 生成路徑字符串
             private String generateDepthString (int depth) {
                   StringBuilder sb = new StringBuilder();
                   for (int i = 0; i < depth; i++) {
                         sb.append("-- ");
                   }
                   return  sb.toString();
             }
       }
  2. Main

    public class Main {
    
             public static void main(String[] args) {
    
                   MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>();
    
                   int [] nums = {5, 3, 6, 8, 4, 2};
                   for (int i = 0; i < nums.length ; i++) {
                         mbst.add(nums[i]);
                   }
    
                   /////////////////
                   //      5      //
                   //    /   \    //
                   //   3    6    //
                   //  / \    \   //
                   // 2  4     8  //
                   /////////////////
    
                   // 前序遍歷
                   mbst.preOrder(); // 5 3 2 4 6 8
                   System.out.println();
    
                   // 中序遍歷
                   mbst.inOrder(); // 2 3 4 5 6 8
                   System.out.println();
    
                   // 後序遍歷
                   mbst.postOrder(); // 2 4 3 8 6 5
                   System.out.println();
    
       //        // 輸出 調試字符串
       //        System.out.println(mbst.toString());
             }
       }

二分搜索樹的遍歷-深刻理解前中後序遍歷

  1. 再看二分搜索樹的遍歷

    1. 對每個節點都有三次的訪問機會,
    2. 在遍歷左子樹以前會去訪問一下這個節點而後才能遍歷它的左子樹,
    3. 在遍歷完左子樹以後纔可以回到這個節點,以後纔會去遍歷它的右子樹,
    4. 在遍歷右子樹以後又回到了這個節點。
    5. 這就是每個節點使用這種遞歸遍歷的方式其實會訪問它三次,
  2. 對二分搜索樹前中後這三種順序的遍歷

    1. 其實就對應於這三個訪問機會是在哪裏進行真正的那個訪問操做,
    2. 在哪裏輸出訪問的這個節點的值,
    3. 是先訪問這個節點後再遍歷它的左右子樹,
    4. 仍是先遍歷左子樹而後訪問這個節點最後遍歷右子樹,
    5. 再或者是 先遍歷左右子樹再訪問這個節點。
    function traverse(node) {
       if (node === null) return;
    
       // 1. 第一個訪問的機會   前
    
       traverse(node.left);
    
       // 2. 第二個訪問的機會   中
    
       traverse(node.right);
    
       // 3. 第三個訪問的機會   後
    }
  3. 二叉樹前中後序遍歷訪問節點的不一樣

    1. 前序遍歷訪問節點都是在第一個訪問機會的位置纔去訪問節點,
    2. 中序遍歷訪問節點都是在第二個訪問機會的位置纔去訪問節點,
    3. 後序遍歷訪問節點都是在第三個訪問機會的位置纔去訪問節點,

二分搜索樹的遍歷-非遞歸寫法

  1. 前序遍歷的遞歸寫法

    1. 前序遍歷是最天然的一種遍歷方式,
    2. 同時也是最經常使用的一種遍歷方式,
    3. 若是沒有特殊狀況的話,
    4. 在大多數狀況下都會使用前序遍歷。
    5. 先訪問這個節點,
    6. 而後訪問這個節點的左子樹,
    7. 再訪問這個節點的右子樹,
    8. 整個過程循環往復。
    9. 前序遍歷的表示先訪問的這個節點。
    function preOrder(node) {
       if (node == null) return;
    
       // ... 要作的事情
       // 訪問該節點
    
       // 先一直往左,而後不斷返回上一層 再向左、終止,
       // 最後整個操做循環往復,直到所有終止。
       preOrder(node.left);
       preOrder(node.right);
    }
  2. 前序遍歷的非遞歸寫法

    1. 使用另一個數據結構來模擬遞歸調用時的系統棧。
    2. 先訪問根節點,將根節點壓入棧,
    3. 而後把棧頂元素拿出來,對這個節點進行操做,
    4. 這個節點操做完畢以後,再訪問這個節點的兩個子樹,
    5. 也就是把這個節點的左右兩個孩子壓入棧中,
    6. 壓入棧的順序是先壓入右孩子、再壓入左孩子,
    7. 這是由於棧是後入先出的,因此要先壓入後續要訪問的那個節點,
    8. 再讓棧頂的元素出棧,對這個節點進行操做,
    9. 這個節點操做完畢以後,再訪問這個節點的兩個子樹,
    10. 可是這個節點是葉子節點,它的兩個孩子都爲空,
    11. 那麼什麼都不用壓入了, 再去取棧頂的元素,
    12. 對這個節點進行操做,這個節點操做完畢以後,
    13. 再訪問這個節點的兩個子樹,可是這個節點也是葉子節點,
    14. 那麼什麼都不用壓入了,棧中也爲空了,整個訪問操做結束。
  3. 不管是非遞歸仍是遞歸的寫法,結果都是一致的

    1. 非遞歸的寫法中,棧的應用是幫助你記錄你下面要訪問的哪些節點,
    2. 這個過程很是像使用棧模擬了一下在系統棧中相應的一個調用,
    3. 至關於在系統棧中記錄下一步依次要訪問哪些節點。
  4. 將遞歸算法轉換爲非遞歸算法

    1. 是棧這種數據結構很是重要的一種應用。
  5. 二分搜索樹遍歷的非遞歸實現比遞歸實現複雜不少

    1. 由於你使用了一個輔助的數據結構才能完成這個過程,
    2. 使用了棧這種數據結構模擬了系統調用棧,
    3. 在算法語意解讀上遠遠比遞歸實現的算法語意解讀要難不少。
  6. 二分搜索樹的中序遍歷和後序遍歷的非遞歸實現更復雜

    1. 尤爲是對於後序遍從來說難度更大,
    2. 可是中序遍歷和後序遍歷的非遞歸實現,實際應用並不普遍。
    3. 可是你能夠嘗試實現中序、後序遍歷的非遞歸實現,
    4. 主要是鍛鍊你算法實現、思惟邏輯實現思路,
    5. 在解決這個問題的過程當中可能會遇到一些困難,
    6. 能夠經過查看網上的資料來解決這個問題,
    7. 這樣的問題有可能會在面試題及考試中出現,
    8. 也就是中序和後序遍歷相應的非遞歸實現。
    9. 在經典的教科書中通常都會有這三種遍歷的非遞歸實現,
    10. 經過二分搜索樹的前序遍歷非遞歸的實現方式中能夠看出,
    11. 徹底可使用模擬系統的棧來完成遞歸轉成非遞歸這樣的操做,
    12. 在慕課上 有一門課《玩轉算法面試》中徹底模擬了系統棧的寫法,
    13. 也就是將前中後序的遍歷都轉成了非遞歸的算法,
    14. 這與經典的教科書上的實現不同,
    15. 可是這種方式對你進一步理解棧這種數據結構仍是二分搜索樹的遍歷
    16. 甚至是系統調用的過程都是頗有意義的。
  7. 對於前序遍從來說不管是遞歸寫法仍是非遞歸寫法

    1. 對於這棵樹來講都是在遍歷的過程當中一直到底,
    2. 這樣的一種遍歷方式也叫深度優先遍歷,
    3. 最終的遍歷結果都會先來到整顆樹最深的地方,
    4. 直到不能再深了纔會開始返回到上一層,
    5. 因此這種遍歷就叫作深度優先遍歷。
    6. 與深度優先遍歷相對應的就是廣度優先遍歷,
    7. 廣度優先遍歷遍歷出來的結果它的順序實際上是
    8. 整個二分搜索樹的一個層序遍歷的順序。

代碼示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    import java.util.Stack;
    
          public class MyBinarySearchTree<E extends Comparable<E>> {
    
                private class Node {
                    public E e;
                    public Node left, right;
    
                    public Node (E e) {
                          this.e = e;
                          left = null;
                          right = null;
                    }
                }
    
                private Node root;
                private int size;
    
                public MyBinarySearchTree () {
                      root = null;
                      size = 0;
                }
    
                public int getSize() {
                      return size;
                }
    
                public boolean isEmpty () {
                      return size == 0;
                }
    
                // 向二分搜索樹中添加一個元素 e
                // 改進:直接調用add
                public void add (E e) {
                      root = add(root, e);
                }
    
                // 向以node爲根的二分搜索樹中插入元素E,遞歸算法
                // 改進:返回插入的新節點後二分搜索樹的根
                private Node add (Node node, E e) {
    
                      // 處理最基本的問題
                      if (node == null) {
                            size ++;
                            return new Node(e);
                      }
    
                      // 空的二叉樹也是叉樹。
                      if (e.compareTo(node.e) < 0) {
                            // 將處理後的結果賦值給node的左子樹
                            node.left =  add(node.left, e);
                      } else if (e.compareTo(node.e) > 0) {
                            // 將處理後的結果賦值給node的右子樹
                            node.right =  add(node.right, e);
                      } // 若是相同 就什麼都不作
    
                      // 最後返回這個node
                      return node;
                }
    
                // 查詢二分搜索數中是否包含某個元素
                public boolean contains (E e) {
                      return contains(root, e);
                }
    
                // 向以node爲根的二分搜索樹 進行查找  遞歸算法
                private boolean contains (Node node, E e) {
    
                      // 解決最基本的問題 也就是遍歷完全部節點都沒有找到
                      if (node == null) {
                            return false;
                      }
    
                      // 若是 e 小於當前節點的e 則向左子樹進發
                      if (e.compareTo(node.e) < 0) {
                          return contains(node.left, e);
                      } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發
                          return contains(node.right, e);
                      } else { // 若是e 等於 當前節點 e 則直接返回true
                            return true;
                      }
                }
    
                // 二分搜索樹的前序遍歷
                public void preOrder () {
                      preOrder(root);
                }
    
                // 前序遍歷以node爲根的二分搜索樹 遞歸算法
                private void preOrder (Node node) {
                      if (node == null) {
                            return;
                      }
    
                      // 輸出
                      System.out.println(node.e);
    
                      preOrder(node.left);
                      preOrder(node.right);
    
          //        // 這種邏輯也是能夠的
          //        if (node != null) {
          //            // 輸出
          //            System.out.println(node.e);
          //
          //            preOrder(node.left);
          //            preOrder(node.right);
          //        }
                }
    
                // 二分搜索樹的前序遍歷 非遞歸算法
                public void preOrderNonRecursive () {
    
                      Stack<Node> stack = new Stack<Node>();
                      stack.push(root);
    
                      Node node = null;
                      while (!stack.isEmpty()) {
                            node = stack.pop();
    
          //            // 第一種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它
          //            if (node != null) {
          //                System.out.println(node.e);
          //                stack.push(node.right);
          //                stack.push(node.left);
          //            }
    
          //            // 第二種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它
          //            if (node == null) continue;
          //
          //            System.out.println(node.e);
          //            stack.push(node.right);
          //            stack.push(node.left);
    
                            // 寫法三 不符合要求就不壓入棧
                            System.out.println(node.e);
    
                            if (node.right != null) {
                                  stack.push(node.right);
                            }
                            if (node.left != null) {
                                  stack.push(node.left);
                            }
                      }
                }
    
                // 二分搜索樹的中序遍歷
                public void inOrder () {
                      inOrder(root);
                }
    
                // 中序遍歷以node爲根的二分搜索樹 遞歸算法
                private void inOrder (Node node) {
                      if (node == null) return;
    
                      inOrder(node.left);
                      System.out.println(node.e);
                      inOrder(node.right);
    
                }
    
                // 二分搜索樹的中序遍歷 非遞歸算法
                public void inOrderNonRecursive () {
                }
    
                // 二分搜索樹的後序遍歷
                public void postOrder () {
                      postOrder(root);
                }
    
                // 後續遍歷以node爲根的二分搜索樹 遞歸算法
                private void postOrder (Node node) {
                      if (node == null) return;
    
                      postOrder(node.left);
                      postOrder(node.right);
                      System.out.println(node.e);
                }
    
                @Override
                public String toString () {
                      StringBuilder sb = new StringBuilder();
                      generateBSTString(root, 0, sb);
                      return sb.toString();
                }
    
                // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串
                private void generateBSTString (Node node, int depath, StringBuilder sb) {
                      if (node == null) {
                            sb.append(generateDepthString(depath) + "null\n");
                            return;
                      }
    
                      sb.append(generateDepthString(depath) + node.e + "\n");
    
                      generateBSTString(node.left, depath + 1, sb);
                      generateBSTString(node.right, depath + 1, sb);
    
                }
    
                // 生成路徑字符串
                private String generateDepthString (int depth) {
                      StringBuilder sb = new StringBuilder();
                      for (int i = 0; i < depth; i++) {
                            sb.append("-- ");
                      }
                      return  sb.toString();
                }
          }
  2. Main

    public class Main {
    
             public static void main(String[] args) {
    
                   MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>();
    
                   int [] nums = {5, 3, 6, 8, 4, 2};
                   for (int i = 0; i < nums.length ; i++) {
                         mbst.add(nums[i]);
                   }
    
                   /////////////////
                   //      5      //
                   //    /   \    //
                   //   3    6    //
                   //  / \    \   //
                   // 2  4     8  //
                   /////////////////
    
                   // 前序遍歷
                   mbst.preOrder(); // 5 3 2 4 6 8
                   System.out.println();
    
                   // 中序遍歷
                   mbst.inOrder(); // 2 3 4 5 6 8
                   System.out.println();
    
                   // 後序遍歷
                   mbst.postOrder(); // 2 4 3 8 6 5
                   System.out.println();
    
                   // 前序遍歷 非遞歸
                   mbst.preOrderNonRecursive(); // 5 3 2 4 6 8
                   System.out.println();
    
       //        // 輸出 調試字符串
       //        System.out.println(mbst.toString());
             }
       }

二分搜索樹的層序遍歷

  1. 二分搜索樹的 前序、中序、後序遍歷

    1. 它們本質上都是深度優先遍歷。
  2. 對於二分搜索樹來講

    1. 每個節點都有一個相應的深度的值,
    2. 根節點做爲深度爲 0 相應的節點,
    3. 有一些教科書 會把根節點做爲深度爲 1 相應的節點,
    4. 若是以計算機世界裏索引的定義爲準那就是使用 0,
    5. 根節點就是第 0 層。
  3. 先遍歷第 0 層、再遍歷第 1 層、再遍歷下一層,

    1. 這樣的一層一層的遍歷就稱爲廣度優先遍歷,
    2. 逐層向下遍歷的節點在廣度上進行拓展,
    3. 這樣的一個遍歷順序就叫作層序遍歷、廣度優先遍歷,
    4. 而不像以前那樣 先順着一個枝杈向着最深的地方走。
  4. 對於層序遍歷的實現或者廣度優先遍歷的實現

    1. 一般不是使用遞歸的方式進行實現的,
    2. 而是使用非遞歸的方式進行實現的,
    3. 而且在其中須要使用另外的一個數據結構隊列,
    4. 從根節點開始排着隊的進入這個隊列,
    5. 隊列中存儲的就是待遍歷的元素,
    6. 每一次遍歷的它的元素以後再將它的左右孩子也排進隊列中,
    7. 整個過程依此類推。
  5. 先入隊根節點,而後看隊首是否有元素,

    1. 有的話就對隊首的元素進行操做,
    2. 操做完畢後就將操做完畢的元素的左右孩子也入隊,
    3. 而後再對隊列中的元素進行操做,
    4. 隊列中的元素又操做完畢了,
    5. 再讓操做完畢的這些元素的左右孩子入隊,
    6. 最後在對隊列中的元素進行操做,
    7. 這些元素都是葉子節點沒有左右孩子了,,
    8. 不用入隊了,隊列中沒有元素,整個過程處理完畢,
    9. 這個處理過程就是一層一層的進行處理的一個順序,
    10. 這就是二分搜索樹的廣度優先遍歷,也叫層序遍歷。
  6. 相對於深度優先遍從來說,廣度優先遍歷的優勢

    1. 它能更快的找到你想要查詢的那個元素,
    2. 這樣的區別主要用於搜索策略上,
    3. 而不是用在遍歷這個操做上,
    4. 雖然遍歷要將整個二叉樹上全部的元素都訪問一遍,
    5. 這種狀況下深度優先遍歷和廣度優先遍歷是沒有區別的。
    6. 可是若是想在一棵樹中找到某一個問題的解,
    7. 那對於深度優先遍從來說
    8. 它會從根節點一股腦的跑到這棵樹很是深的地方,
    9. 可是頗有可能這個問題的解並不在那麼深的地方而是很淺的地方,
    10. 這樣一來深度優先遍歷要花很長時間才能訪問到這個很淺的地方,
    11. 例如前序遍歷,若是這個問題的解在右子樹上很淺的位置,
    12. 你從一開始就從根節點遍歷到左子樹的最深處,那就不必了,
    13. 可是這個經常使用於算法設計中,如無權圖的最短路徑,
    14. 樹這種結構在算法設計裏也有很是重要的應用,
    15. 尤爲是不少時候設計出一個算法,可能真正不須要把這個樹發現出來,
    16. 可是這個算法的整個過程就是在一棵虛擬的樹中完成的。
  7. 在圖中也是有深度優先遍歷和廣度優先遍歷的

    1. 在樹中和圖中進行深度優先遍歷其實它們的實質是同樣的,
    2. 不一樣的點,對於圖來講須要記錄一下對於某一個節點以前是否曾經遍歷過,
    3. 由於對於圖來講每個節點的前驅或者放在樹這個模型中
    4. 相應的術語就是每一節點它的父親可能有多個,
    5. 從而產生重複訪問這樣的問題,而這樣的問題在樹結構中是不存在的,
    6. 因此在圖結構中須要作一個相應的記錄。

代碼示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    import java.util.LinkedList;
       import java.util.Queue;
       import java.util.Stack;
    
       public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             // 改進:直接調用add
             public void add (E e) {
                   root = add(root, e);
             }
    
             // 向以node爲根的二分搜索樹中插入元素E,遞歸算法
             // 改進:返回插入的新節點後二分搜索樹的根
             private Node add (Node node, E e) {
    
                   // 處理最基本的問題
                   if (node == null) {
                         size ++;
                         return new Node(e);
                   }
    
                   // 空的二叉樹也是叉樹。
                   if (e.compareTo(node.e) < 0) {
                         // 將處理後的結果賦值給node的左子樹
                         node.left =  add(node.left, e);
                   } else if (e.compareTo(node.e) > 0) {
                         // 將處理後的結果賦值給node的右子樹
                         node.right =  add(node.right, e);
                   } // 若是相同 就什麼都不作
    
                   // 最後返回這個node
                   return node;
             }
    
             // 查詢二分搜索數中是否包含某個元素
             public boolean contains (E e) {
                   return contains(root, e);
             }
    
             // 向以node爲根的二分搜索樹 進行查找  遞歸算法
             private boolean contains (Node node, E e) {
    
                   // 解決最基本的問題 也就是遍歷完全部節點都沒有找到
                   if (node == null) {
                         return false;
                   }
    
                   // 若是 e 小於當前節點的e 則向左子樹進發
                   if (e.compareTo(node.e) < 0) {
                       return contains(node.left, e);
                   } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發
                       return contains(node.right, e);
                   } else { // 若是e 等於 當前節點 e 則直接返回true
                         return true;
                   }
             }
    
             // 二分搜索樹的前序遍歷
             public void preOrder () {
                   preOrder(root);
             }
    
             // 前序遍歷以node爲根的二分搜索樹 遞歸算法
             private void preOrder (Node node) {
                   if (node == null) {
                         return;
                   }
    
                   // 輸出
                   System.out.println(node.e);
    
                   preOrder(node.left);
                   preOrder(node.right);
    
       //        // 這種邏輯也是能夠的
       //        if (node != null) {
       //            // 輸出
       //            System.out.println(node.e);
       //
       //            preOrder(node.left);
       //            preOrder(node.right);
       //        }
             }
    
             // 二分搜索樹的前序遍歷 非遞歸算法
             public void preOrderNonRecursive () {
    
                   Stack<Node> stack = new Stack<Node>();
                   stack.push(root);
    
                   Node node = null;
                   while (!stack.isEmpty()) {
                         node = stack.pop();
    
       //            // 第一種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它
       //            if (node != null) {
       //                System.out.println(node.e);
       //                stack.push(node.right);
       //                stack.push(node.left);
       //            }
    
       //            // 第二種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它
       //            if (node == null) continue;
       //
       //            System.out.println(node.e);
       //            stack.push(node.right);
       //            stack.push(node.left);
    
                         // 寫法三 不符合要求就不壓入棧
                         System.out.println(node.e);
    
                         if (node.right != null) {
                               stack.push(node.right);
                         }
                         if (node.left != null) {
                               stack.push(node.left);
                         }
                   }
             }
    
             // 二分搜索樹的中序遍歷
             public void inOrder () {
                   inOrder(root);
             }
    
             // 中序遍歷以node爲根的二分搜索樹 遞歸算法
             private void inOrder (Node node) {
                   if (node == null) return;
    
                   inOrder(node.left);
                   System.out.println(node.e);
                   inOrder(node.right);
    
             }
    
             // 二分搜索樹的中序遍歷 非遞歸算法
             public void inOrderNonRecursive () {
             }
    
             // 二分搜索樹的後序遍歷
             public void postOrder () {
                   postOrder(root);
             }
    
             // 後續遍歷以node爲根的二分搜索樹 遞歸算法
             private void postOrder (Node node) {
                   if (node == null) return;
    
                   postOrder(node.left);
                   postOrder(node.right);
                   System.out.println(node.e);
             }
    
             // 二分搜索樹的層序遍歷
             public void levelOrder () {
    
                   // java中的Queue是一個接口,可是它有鏈表和隊列的實現,
                   // 因此你能夠new 一個子類鏈表類來進行進行使用,能夠達到一樣的效果
                   Queue<Node> queue = new LinkedList<Node>();
                   queue.add(root);
    
                   while (!queue.isEmpty()) {
                         Node node = queue.remove();
                         System.out.println(node.e);
    
                         if (node.left != null) {
                               queue.add(node.left);
                         }
                         if (node.right != null) {
                               queue.add(node.right);
                         }
                   }
             }
    
             @Override
             public String toString () {
                   StringBuilder sb = new StringBuilder();
                   generateBSTString(root, 0, sb);
                   return sb.toString();
             }
    
             // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串
             private void generateBSTString (Node node, int depath, StringBuilder sb) {
                   if (node == null) {
                         sb.append(generateDepthString(depath) + "null\n");
                         return;
                   }
    
                   sb.append(generateDepthString(depath) + node.e + "\n");
    
                   generateBSTString(node.left, depath + 1, sb);
                   generateBSTString(node.right, depath + 1, sb);
    
             }
    
             // 生成路徑字符串
             private String generateDepthString (int depth) {
                   StringBuilder sb = new StringBuilder();
                   for (int i = 0; i < depth; i++) {
                         sb.append("-- ");
                   }
                   return  sb.toString();
             }
       }
  2. Main

    public class Main {
    
             public static void main(String[] args) {
    
                   MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>();
    
                   int [] nums = {5, 3, 6, 8, 4, 2};
                   for (int i = 0; i < nums.length ; i++) {
                         mbst.add(nums[i]);
                   }
    
                   /////////////////
                   //      5      //
                   //    /   \    //
                   //   3    6    //
                   //  / \    \   //
                   // 2  4     8  //
                   /////////////////
    
       //        // 前序遍歷
       //        mbst.preOrder(); // 5 3 2 4 6 8
       //        System.out.println();
       //
       //        // 中序遍歷
       //        mbst.inOrder(); // 2 3 4 5 6 8
       //        System.out.println();
       //
       //        // 後序遍歷
       //        mbst.postOrder(); // 2 4 3 8 6 5
       //        System.out.println();
       //
       //        // 前序遍歷 非遞歸
       //        mbst.preOrderNonRecursive(); // 5 3 2 4 6 8
       //        System.out.println();
    
                   mbst.levelOrder(); // 5 3 6 2 4 8
                   System.out.println();
    
       //        // 輸出 調試字符串
       //        System.out.println(mbst.toString());
             }
       }

學習方法

  1. 不少時候學習知識

    1. 並非簡單的一起一起把它們學過了就能夠了,
    2. 不少時候要想可以達到靈活運用可以達到理解的深入,都須要進行比對,
    3. 刻意的去找到從不一樣方法之間它們的區別和聯繫,
    4. 以及本身去總結不一樣的方法適用於什麼樣的場合,
    5. 只有這樣,這些知識才可以在你的腦海中才不是一個一個的碎片,
    6. 而是有機的聯繫起來的,面對不一樣的問題才能很是的快的
    7. 而且準確的說出來用怎樣的方法去解決更加的好。

二分搜索樹的刪除節點-刪除最大最小值

  1. 對於二分搜索樹來講刪除一個節點相對來講是比較複雜的

    1. 能夠先對這個操做進行拆解,從最簡單的開始。
  2. 刪除二分搜索樹的最小值和最大值

    1. 刪除二分搜索樹中任意元素會複用到
    2. 刪除二分搜索樹最大值和最小值相應的邏輯。
    3. 要想刪除二分搜索樹中最大值和最小值,
    4. 那麼就要先找到二分搜索樹中的最大值和最小值。
  3. 找到二分搜索樹中的最大值和最小值是很是容易的

    1. 每個節點的左子樹上全部的節點的值都小於當前這個節點,
    2. 每個節點的右子樹上全部的節點的值都大於當前這個節點,
    3. 那麼從根節點開始一直向左,直到不能再左了,就能找到最小值,
    4. 反之從根節點開始一直向右,知道不能再右了,就能找到最大值。
    5. 這個操做就像操做鏈表同樣,就像是在找一條鏈上的尾節點。
  4. 刪除最大元素節點

    1. 要刪除最大元素的這個節點可能有左孩子節點可是沒有右孩子節點,
    2. 因此可能會致使沒法繼續向右因而遞歸就終止了,
    3. 那麼這個時候刪除這個節點能夠採用當前節點的左孩子替代當前這個節點,
    4. 覆蓋操做也算是刪除了當前這個節點了。
    5. 若是你像返回被刪除的這個最大元素節點,你能夠先查詢出這個最大的元素節點,
    6. 而後存到一個變量中,最後再調用刪除這個最大元素節點的方法,最終返回存的這個變量。
  5. 刪除最小元素節點

    1. 要刪除的最小元素的節點可能有右孩子節點可是沒有左孩子節點,
    2. 會致使沒法繼續向左而遞歸終止,你不能刪除這個節點的同時連右孩子一塊兒刪除,
    3. 因此這個時候刪除這個節點能夠採用當前節點的右孩子替代當前這個節點,
    4. 覆蓋操做也算是刪除了當前這個節點了,
    5. 其它的和刪除最大元素同樣,先查詢出來,而後存起來,刪除這個最大元素後,
    6. 再返回以前存起來的最大元素的變量。

代碼示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    import java.util.LinkedList;
       import java.util.Queue;
       import java.util.Stack;
    
       public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             // 改進:直接調用add
             public void add (E e) {
                   root = add(root, e);
             }
    
             // 向以node爲根的二分搜索樹中插入元素E,遞歸算法
             // 改進:返回插入的新節點後二分搜索樹的根
             private Node add (Node node, E e) {
    
                   // 處理最基本的問題
                   if (node == null) {
                         size ++;
                         return new Node(e);
                   }
    
                   // 空的二叉樹也是叉樹。
                   if (e.compareTo(node.e) < 0) {
                         // 將處理後的結果賦值給node的左子樹
                         node.left =  add(node.left, e);
                   } else if (e.compareTo(node.e) > 0) {
                         // 將處理後的結果賦值給node的右子樹
                         node.right =  add(node.right, e);
                   } // 若是相同 就什麼都不作
    
                   // 最後返回這個node
                   return node;
             }
    
             // 查詢二分搜索數中是否包含某個元素
             public boolean contains (E e) {
                   return contains(root, e);
             }
    
             // 向以node爲根的二分搜索樹 進行查找  遞歸算法
             private boolean contains (Node node, E e) {
    
                   // 解決最基本的問題 也就是遍歷完全部節點都沒有找到
                   if (node == null) {
                         return false;
                   }
    
                   // 若是 e 小於當前節點的e 則向左子樹進發
                   if (e.compareTo(node.e) < 0) {
                       return contains(node.left, e);
                   } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發
                       return contains(node.right, e);
                   } else { // 若是e 等於 當前節點 e 則直接返回true
                         return true;
                   }
             }
    
             // 二分搜索樹的前序遍歷
             public void preOrder () {
                   preOrder(root);
             }
    
             // 前序遍歷以node爲根的二分搜索樹 遞歸算法
             private void preOrder (Node node) {
                   if (node == null) {
                         return;
                   }
    
                   // 輸出
                   System.out.println(node.e);
    
                   preOrder(node.left);
                   preOrder(node.right);
    
       //        // 這種邏輯也是能夠的
       //        if (node != null) {
       //            // 輸出
       //            System.out.println(node.e);
       //
       //            preOrder(node.left);
       //            preOrder(node.right);
       //        }
             }
    
             // 二分搜索樹的前序遍歷 非遞歸算法
             public void preOrderNonRecursive () {
    
                   Stack<Node> stack = new Stack<Node>();
                   stack.push(root);
    
                   Node node = null;
                   while (!stack.isEmpty()) {
                         node = stack.pop();
    
       //            // 第一種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它
       //            if (node != null) {
       //                System.out.println(node.e);
       //                stack.push(node.right);
       //                stack.push(node.left);
       //            }
    
       //            // 第二種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它
       //            if (node == null) continue;
       //
       //            System.out.println(node.e);
       //            stack.push(node.right);
       //            stack.push(node.left);
    
                         // 寫法三 不符合要求就不壓入棧
                         System.out.println(node.e);
    
                         if (node.right != null) {
                               stack.push(node.right);
                         }
                         if (node.left != null) {
                               stack.push(node.left);
                         }
                   }
             }
    
             // 二分搜索樹的中序遍歷
             public void inOrder () {
                   inOrder(root);
             }
    
             // 中序遍歷以node爲根的二分搜索樹 遞歸算法
             private void inOrder (Node node) {
                   if (node == null) return;
    
                   inOrder(node.left);
                   System.out.println(node.e);
                   inOrder(node.right);
    
             }
    
             // 二分搜索樹的中序遍歷 非遞歸算法
             public void inOrderNonRecursive () {
             }
    
             // 二分搜索樹的後序遍歷
             public void postOrder () {
                   postOrder(root);
             }
    
             // 後續遍歷以node爲根的二分搜索樹 遞歸算法
             private void postOrder (Node node) {
                   if (node == null) return;
    
                   postOrder(node.left);
                   postOrder(node.right);
                   System.out.println(node.e);
             }
    
             // 二分搜索樹的層序遍歷
             public void levelOrder () {
    
                   // java中的Queue是一個接口,可是它有鏈表和隊列的實現,
                   // 因此你能夠new 一個子類鏈表類來進行進行使用,能夠達到一樣的效果
                   Queue<Node> queue = new LinkedList<Node>();
                   queue.add(root);
    
                   while (!queue.isEmpty()) {
                         Node node = queue.remove();
                         System.out.println(node.e);
    
                         if (node.left != null) {
                               queue.add(node.left);
                         }
                         if (node.right != null) {
                               queue.add(node.right);
                         }
                   }
             }
    
             // 尋找二分搜索樹的最小值元素
             public E minimum () {
                   if (size == 0) {
                         throw new IllegalArgumentException("BST is empty.");
                   }
    
                   return minimum(root).e;
             }
    
             // 返回以node爲根的二分搜索樹的最小值所在的節點
             private Node minimum (Node node) {
                   // 向左走再也走不動了,就返回這個節點。
                   if (node.left == null) return node;
    
                   return minimum(node.left);
             }
    
             // 從二分搜索樹種刪除最小值所在節點,返回這個最小值
             public E removeMin () {
                   E result = minimum();
       //        removeMin(root);
                   root = removeMin(root);
                   return result;
             }
    
             // 刪除掉以node爲根的二分搜索樹中的最小節點
             // 返回刪除節點後新的二分搜索樹的根
             private Node removeMin (Node node) {
       //        if (node.left == null) {
       //            node = node.right;
       //            size --;
       //            return node;
       //        }
       //
       //        return removeMin(node.left);
    
                   if (node.left == null) {
                         Node rightNode = node.right;
                         node.right = null;
                         size --;
                         return rightNode;
                   }
    
                   node.left = removeMin(node.left);
                   return node;
             }
    
             // 尋找二分搜索樹的最大值元素
             public E maximum () {
                   if (size == 0) {
                         throw new IllegalArgumentException("BST is empty.");
                   }
    
                   return maximum(root).e;
             }
    
             // 返回以node爲根的二分搜索樹的最大值所在的節點
             private Node maximum (Node node) {
                   // 向右走再也走不動了,就返回這個節點。
                   if (node.right == null) return node;
    
                   return maximum(node.right);
             }
    
             // 從二分搜索樹種刪除最大值所在節點,返回這個最大值
             public E removeMax () {
                   E result = maximum();
                   root = removeMax(root);
                   return result;
             }
    
             // 刪除掉以node爲根的二分搜索樹中的最大節點
             // 返回刪除節點後新的二分搜索樹的根
             private Node removeMax (Node node) {
    
                   if (node.right == null) {
                         Node leftNode = node.left;
                         node.left = null;
                         size --;
                         return leftNode;
                   }
    
                   node.right = removeMax(node.right);
                   return node;
             }
    
             @Override
             public String toString () {
                   StringBuilder sb = new StringBuilder();
                   generateBSTString(root, 0, sb);
                   return sb.toString();
             }
    
             // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串
             private void generateBSTString (Node node, int depath, StringBuilder sb) {
                   if (node == null) {
                         sb.append(generateDepthString(depath) + "null\n");
                         return;
                   }
    
                   sb.append(generateDepthString(depath) + node.e + "\n");
    
                   generateBSTString(node.left, depath + 1, sb);
                   generateBSTString(node.right, depath + 1, sb);
    
             }
    
             // 生成路徑字符串
             private String generateDepthString (int depth) {
                   StringBuilder sb = new StringBuilder();
                   for (int i = 0; i < depth; i++) {
                         sb.append("-- ");
                   }
                   return  sb.toString();
             }
       }
  2. Main

    import java.util.ArrayList;
       import java.util.Random;
    
       public class Main {
    
             public static void main(String[] args) {
    
                   MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>();
    
                   Random random = new Random();
                   int n = 100;
    
                   for (int i = 0; i < n; i++) {
                         mbst.add(random.nextInt(Integer.MAX_VALUE));
                   }
    
                   // 動態數組
                   ArrayList<Integer> arrayList = new ArrayList<Integer>();
                   while (!mbst.isEmpty()) {
                         arrayList.add(mbst.removeMin());
       //            arrayList.add(mbst.removeMax());
                   }
    
                   // 數組中就是從小到大排序的
                   System.out.println(arrayList);
    
                   // 驗證一下
                   for (int i = 1; i < arrayList.size() ; i++) {
                         // 若是前面的數大於後面的數就報異常
                         if (arrayList.get(i - 1) > arrayList.get(i)) {
                               // 若是前面的數小於後面的數就報異常
       //            if (arrayList.get(i - 1) < arrayList.get(i)) {
                               throw new IllegalArgumentException("error.");
                         }
                   }
                   System.out.println("removeMin test completed.");
       //        System.out.println("removeMax test completed.");
             }
       }

二分搜索樹的刪除節點-刪除任意元素

  1. 在二分搜索樹種刪除最大值最小值的邏輯

    1. 從根節點開始,向左或者向右遍歷,
    2. 遍歷到最左或者最右時,
    3. 記錄這個節點的右子樹或者左子樹,
    4. 而後返回,而後讓這條分支上每一個節點的左或者右子樹進行層層覆蓋,
    5. 而後層層返回新的節點,直到最後返回給根節點、覆蓋掉根節點,
    6. 從而達到了刪除最小或最大節點的目的。
    7. 刪除最小值的節點就不停的向左遍歷,最後記錄右子樹,
    8. 由於被刪除的節點要被這個節點的右子樹替代掉,
    9. 只有這樣纔可以達到刪除最小值的節點的效果。
    10. 刪除最大值的節點就不停的向右遍歷,最後記錄左子樹,
    11. 由於被刪除的節點要被這個節點的左子樹替代掉,
    12. 只有這樣纔可以達到刪除最大值的節點的效果。
  2. 刪除二分搜索樹上任意節點會發生的狀況

    1. 刪除的這個節點只有左孩子,這個邏輯和上面的相似,
    2. 就讓這個節點的左孩子取代這個節點的位置。
    3. 刪除的這個節點只有右孩子,這個邏輯也是同樣,
    4. 就讓這個節點的右孩子取代這個節點的位置。
    5. 刪除的這個節點是葉子節點,這個邏輯也同樣,
    6. 由於 null 也是一個二分搜索樹、也是一個節點、也是一個孩子,
    7. 直接讓 null 取代這個節點的位置便可。
    8. 真正難的地方是去刪除左右都有孩子這樣的節點,
    9. 在 1962 年,Hibbard(計算機科學家)提出-Hibbard Deletion,
    10. 找到離這個節點的值最近而且大的那個節點來取代這個節點,
    11. 也就是找到 這個節點的右孩子的左孩子(右子樹的左子樹上最小的節點),
    12. 例如待刪除的節點爲 d,那麼就是 s = min(d->right),
    13. 找到比當前節點大最小且最近的節點,這個 s 就是 d 的後繼,
    14. 執行 s->right = delMin(d->right)這樣的操做,
    15. 以後讓 s->left = d->left,
    16. 刪除的 d 後,s 是新的子樹的根,返回這個 s 節點就能夠了。
    17. 除了找待刪除節點 d 的後繼 s 以外,還能夠找待刪除節點的前驅 p,
    18. 也就是找到 這個節點的左孩子的右孩子(左子樹的右子樹上最大的節點)。
    19. 不管使用前驅仍是後繼來取代待刪除的這個節點
    20. 都可以繼續保持二分搜索樹的性質。
  3. 對於二分搜索樹來講

    1. 相對於數組、棧、隊列、鏈表這些數據結構要複雜一些,
    2. 二分搜索樹自己也是學習其它的樹,如 平衡二叉樹的基礎。

代碼示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    import java.util.LinkedList;
       import java.util.Queue;
       import java.util.Stack;
    
       public class MyBinarySearchTree<E extends Comparable<E>> {
    
             private class Node {
                 public E e;
                 public Node left, right;
    
                 public Node (E e) {
                       this.e = e;
                       left = null;
                       right = null;
                 }
             }
    
             private Node root;
             private int size;
    
             public MyBinarySearchTree () {
                   root = null;
                   size = 0;
             }
    
             public int getSize() {
                   return size;
             }
    
             public boolean isEmpty () {
                   return size == 0;
             }
    
             // 向二分搜索樹中添加一個元素 e
             // 改進:直接調用add
             public void add (E e) {
                   root = add(root, e);
             }
    
             // 向以node爲根的二分搜索樹中插入元素E,遞歸算法
             // 改進:返回插入的新節點後二分搜索樹的根
             private Node add (Node node, E e) {
    
                   // 處理最基本的問題
                   if (node == null) {
                         size ++;
                         return new Node(e);
                   }
    
                   // 空的二叉樹也是叉樹。
                   if (e.compareTo(node.e) < 0) {
                         // 將處理後的結果賦值給node的左子樹
                         node.left =  add(node.left, e);
                   } else if (e.compareTo(node.e) > 0) {
                         // 將處理後的結果賦值給node的右子樹
                         node.right =  add(node.right, e);
                   } // 若是相同 就什麼都不作
    
                   // 最後返回這個node
                   return node;
             }
    
             // 查詢二分搜索數中是否包含某個元素
             public boolean contains (E e) {
                   return contains(root, e);
             }
    
             // 向以node爲根的二分搜索樹 進行查找  遞歸算法
             private boolean contains (Node node, E e) {
    
                   // 解決最基本的問題 也就是遍歷完全部節點都沒有找到
                   if (node == null) {
                         return false;
                   }
    
                   // 若是 e 小於當前節點的e 則向左子樹進發
                   if (e.compareTo(node.e) < 0) {
                       return contains(node.left, e);
                   } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發
                       return contains(node.right, e);
                   } else { // 若是e 等於 當前節點 e 則直接返回true
                         return true;
                   }
             }
    
             // 二分搜索樹的前序遍歷
             public void preOrder () {
                   preOrder(root);
             }
    
             // 前序遍歷以node爲根的二分搜索樹 遞歸算法
             private void preOrder (Node node) {
                   if (node == null) {
                         return;
                   }
    
                   // 輸出
                   System.out.println(node.e);
    
                   preOrder(node.left);
                   preOrder(node.right);
    
       //        // 這種邏輯也是能夠的
       //        if (node != null) {
       //            // 輸出
       //            System.out.println(node.e);
       //
       //            preOrder(node.left);
       //            preOrder(node.right);
       //        }
             }
    
             // 二分搜索樹的前序遍歷 非遞歸算法
             public void preOrderNonRecursive () {
    
                   Stack<Node> stack = new Stack<Node>();
                   stack.push(root);
    
                   Node node = null;
                   while (!stack.isEmpty()) {
                         node = stack.pop();
    
       //            // 第一種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它
       //            if (node != null) {
       //                System.out.println(node.e);
       //                stack.push(node.right);
       //                stack.push(node.left);
       //            }
    
       //            // 第二種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它
       //            if (node == null) continue;
       //
       //            System.out.println(node.e);
       //            stack.push(node.right);
       //            stack.push(node.left);
    
                         // 寫法三 不符合要求就不壓入棧
                         System.out.println(node.e);
    
                         if (node.right != null) {
                               stack.push(node.right);
                         }
                         if (node.left != null) {
                               stack.push(node.left);
                         }
                   }
             }
    
             // 二分搜索樹的中序遍歷
             public void inOrder () {
                   inOrder(root);
             }
    
             // 中序遍歷以node爲根的二分搜索樹 遞歸算法
             private void inOrder (Node node) {
                   if (node == null) return;
    
                   inOrder(node.left);
                   System.out.println(node.e);
                   inOrder(node.right);
    
             }
    
             // 二分搜索樹的中序遍歷 非遞歸算法
             public void inOrderNonRecursive () {
             }
    
             // 二分搜索樹的後序遍歷
             public void postOrder () {
                   postOrder(root);
             }
    
             // 後續遍歷以node爲根的二分搜索樹 遞歸算法
             private void postOrder (Node node) {
                   if (node == null) return;
    
                   postOrder(node.left);
                   postOrder(node.right);
                   System.out.println(node.e);
             }
    
             // 二分搜索樹的層序遍歷
             public void levelOrder () {
    
                   // java中的Queue是一個接口,可是它有鏈表和隊列的實現,
                   // 因此你能夠new 一個子類鏈表類來進行進行使用,能夠達到一樣的效果
                   Queue<Node> queue = new LinkedList<Node>();
                   queue.add(root);
    
                   while (!queue.isEmpty()) {
                         Node node = queue.remove();
                         System.out.println(node.e);
    
                         if (node.left != null) {
                               queue.add(node.left);
                         }
                         if (node.right != null) {
                               queue.add(node.right);
                         }
                   }
             }
    
             // 尋找二分搜索樹的最小值元素
             public E minimum () {
                   if (size == 0) {
                         throw new IllegalArgumentException("BST is empty.");
                   }
    
                   return minimum(root).e;
             }
    
             // 返回以node爲根的二分搜索樹的最小值所在的節點
             private Node minimum (Node node) {
                   // 向左走再也走不動了,就返回這個節點。
                   if (node.left == null) return node;
    
                   return minimum(node.left);
             }
    
             // 從二分搜索樹種刪除最小值所在節點,返回這個最小值
             public E removeMin () {
                   E result = minimum();
       //        removeMin(root);
                   root = removeMin(root);
                   return result;
             }
    
             // 刪除掉以node爲根的二分搜索樹中的最小節點
             // 返回刪除節點後新的二分搜索樹的根
             private Node removeMin (Node node) {
       //        if (node.left == null) {
       //            node = node.right;
       //            size --;
       //            return node;
       //        }
       //
       //        return removeMin(node.left);
    
                   if (node.left == null) {
                         Node rightNode = node.right;
                         node.right = null;
                         size --;
                         return rightNode;
                   }
    
                   node.left = removeMin(node.left);
                   return node;
             }
    
             // 尋找二分搜索樹的最大值元素
             public E maximum () {
                   if (size == 0) {
                         throw new IllegalArgumentException("BST is empty.");
                   }
    
                   return maximum(root).e;
             }
    
             // 返回以node爲根的二分搜索樹的最大值所在的節點
             private Node maximum (Node node) {
                   // 向右走再也走不動了,就返回這個節點。
                   if (node.right == null) return node;
    
                   return maximum(node.right);
             }
    
             // 從二分搜索樹種刪除最大值所在節點,返回這個最大值
             public E removeMax () {
                   E result = maximum();
                   root = removeMax(root);
                   return result;
             }
    
             // 刪除掉以node爲根的二分搜索樹中的最大節點
             // 返回刪除節點後新的二分搜索樹的根
             private Node removeMax (Node node) {
    
                   if (node.right == null) {
                         Node leftNode = node.left;
                         node.left = null;
                         size --;
                         return leftNode;
                   }
    
                   node.right = removeMax(node.right);
                   return node;
             }
    
             // 從二分搜索樹中刪除元素e的節點
             public void remove (E e) {
                   root = remove(root, e);
             }
    
             // 刪除掉以node爲根的二分搜索樹中值爲e的節點 遞歸算法
             // 返回刪除節點後新的二分搜索樹的根
             private Node remove(Node node, E e) {
    
                   if (node == null) return null;
    
                   if (e.compareTo(node.e) < 0) {
                         node.left = remove(node.left, e);
                         return node;
                   } else if (e.compareTo(node.e) > 0) {
                         node.right = remove(node.right, e);
                         return node;
                   } else { // e == node.e
    
                         // 待刪除的節點左子樹爲空
                         if (node.left == null) {
                               Node rightNode = node.right;
                               node.right = null;
                               size --;
                               return rightNode;
                         }
    
                         // 待刪除的節點右子樹爲空
                         if (node.right == null) {
                               Node leftNode = node.left;
                               node.left = null;
                               size --;
                               return leftNode;
                         }
    
                         // 待刪除的節點左右子樹都不爲空的狀況
                         // 找到比待刪除節點大的最小節點,即待刪除節點右子樹的最小節點
                         // 用這個節點頂替待刪除節點的位置
                         Node successor = minimum(node.right);
                         successor.right = removeMin(node.right);
    
                         // 在removeMin這個操做中維護了一次size --,可是並無刪除節點
                         // 因此這裏要進行一次size ++操做
                         size ++;
                         successor.left = node.left;
    
                         // 讓node這個節點與當前這個二分搜索樹脫離關係
                         node.left = node.right = null;
                         // 維護一下size
                         size --;
    
                         return successor;
                   }
             }
    
             @Override
             public String toString () {
                   StringBuilder sb = new StringBuilder();
                   generateBSTString(root, 0, sb);
                   return sb.toString();
             }
    
             // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串
             private void generateBSTString (Node node, int depath, StringBuilder sb) {
                   if (node == null) {
                         sb.append(generateDepthString(depath) + "null\n");
                         return;
                   }
    
                   sb.append(generateDepthString(depath) + node.e + "\n");
    
                   generateBSTString(node.left, depath + 1, sb);
                   generateBSTString(node.right, depath + 1, sb);
    
             }
    
             // 生成路徑字符串
             private String generateDepthString (int depth) {
                   StringBuilder sb = new StringBuilder();
                   for (int i = 0; i < depth; i++) {
                         sb.append("-- ");
                   }
                   return  sb.toString();
             }
       }
  2. Main

    import java.util.ArrayList;
       import java.util.Random;
    
       public class Main {
    
             public static void main(String[] args) {
    
                   MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>();
                   // 動態數組
                   ArrayList<Integer> arrayList = new ArrayList<Integer>();
    
                   Random random = new Random();
                   int n = 10;
    
                   for (int i = 0; i < n; i++) {
                         int value = random.nextInt(Integer.MAX_VALUE);
                         mbst.add(value);
                         arrayList.add(value);
                   }
    
                   // 輸出二分搜索樹
                   System.out.println(mbst.getSize());
                   // 輸出數組中內容
                   System.out.println(arrayList);
    
                   for (int i = 0; i < arrayList.size(); i++) {
                         mbst.remove(arrayList.get(i));
                   }
    
                   // 輸出二分搜索樹
                   System.out.println(mbst.getSize());
    
                   System.out.println("remove test completed.");
             }
       }

更多與二分搜索樹相關

已經實現的二分搜索樹功能

  1. 添加元素 add
  2. 刪除元素 remove
  3. 查詢元素 contains
  4. 遍歷元素 order

其它實現的二分搜索樹功能

  1. 能夠很是方便的拿到二分搜索樹中最大值和最小值,

    1. 這是由於二分搜索樹自己有一個很是重要的特性,
    2. 也就是二分搜索樹具備順序性,
    3. 這個順序性就是指 二分搜索樹中全部的元素都是有序的,
    4. 例如使用中序遍歷遍歷的元素就是將元素從小到大排列起來,
    5. 也正是有順序性纔可以很方便的得到
    6. 二分搜索樹中最大值(maximum)最小值(minimum),
    7. 包括給定一個值能夠拿到它的前驅(predecessor)和後繼(successor)。
  2. 也由於這個順序性也能夠對它進行 floor 和 ceil 的操做,

    1. 也就是找比某一個元素值大的元素或者值小的元素,
    2. 前驅、後繼中指定的元素必定要在這棵二分搜索樹中,
    3. 而 floor 和 ceil 中指定的這個元素能夠不在這棵二分搜索樹中。
  3. 相應的二分搜索樹還能夠實現 rank 和 select 方法,

    1. rank 也就是指定一個元素找到它的排名,
    2. select 是一個反向的操做,也就是找到排名爲多少名的那個元素。
    3. 對於二分搜索樹來講均可以很是容易的實現這兩個操做。
    4. 實現 rank 和 select 最好的方式是對於二分搜索樹每個節點
    5. 同時還維護一個 size,
    6. 這個 size 就是指以這個節點爲根的二分搜索樹有多少個元素,
    7. 也就是每個節點爲根的二分搜索樹中有多少的元素,
    8. 那麼這個 size 就爲多少,
    9. 也就是每個節點包括本身以及下面的子節點的個數,
    10. 每個 node 在維護了一個 size 以後,
    11. 那麼實現 rank 和 select 這兩個操做就會容易不少,
    12. 也就是給 node 這個成員變量添加一個 size,
    13. 那麼對於二分搜索樹其它操做如添加和刪除操做時,
    14. 也要去維護一下這個節點的 size,
    15. 只有這樣實現這個 rank 和 select 就會很是簡單,
    16. 這樣作以後,對於整棵二分搜索樹而言,
    17. 就再也不須要二分搜索樹的 size 變量了,
    18. 若是要看整棵二分搜索樹有多少個節點,
    19. 直接看root.size就行了,很是的方便。
  4. 維護 depth 的二分搜索樹

    1. 對於二分搜索樹的每個節點還能夠維護一個深度值,
    2. 也就是這個節點的高度值,也就是這個節點處在第幾層的位置,
    3. 維護這個值在一些狀況下是很是有幫助的。
  5. 支持重複元素的二分搜索樹

    1. 只須要定義每個根節點的左子樹全部的節點都是
    2. 小於等於這個根節點值的,
    3. 而每個根節點的右子樹全部的節點都是大於這個根節點值的,
    4. 這樣的定義就很好的支持了重複元素的二叉樹的實現。
  6. 還能夠經過維護每個節點的 count 變量來實現重複元素的二分搜索樹,

    1. 也就是記錄一下這個節點所表明的元素在這個二分搜索樹中存儲的個數,
    2. 當你添加進重複的節點後,直接讓相應節點的count++便可,
    3. 若是你刪除這個重複的節點時,直接讓相應節點的count--便可,
    4. 若是 count 減減以後爲 0,那麼就從二分搜索樹中真正刪除掉。

其它

  1. 在二分搜索樹中相應的變種其實大可能是在 node 中維護一些數據

    1. 就能夠方便你進行一些其它特殊狀況的處理,
  2. 相關的習題能夠去 leetcode 中找到,

    1. 樹標籤:https://leetcode-cn.com/tag/tree/
    2. 如第一題,二叉樹的最大深度,這個題和鏈表是很是像的,
    3. 它有一個答題的模板,你提交的時候要按照這個模板來進行提交。
  3. 其它

    1. 二分搜索樹的複雜度分析,
    2. 二分搜索樹有兩個重要的應用集合和映射,
    3. 其實用數組和鏈表也可以實現集合和映射,
    4. 二分搜索樹也有它的侷限性。
相關文章
相關標籤/搜索