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

思惟導圖

前言

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

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

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

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

樹結構

  1. 線性數據結構是把全部的數據排成一排
    1. 樹結構是倒立的樹,由一個根節點延伸出不少新的分支節點。
  2. 樹結構自己是一個種自然的組織結構
    1. 如 電腦中文件夾目錄結構就是樹結構
    2. 這種結構來源於生活,
    3. 好比 圖書館總體分紅幾個大館,
    4. 如 數理館、文史館等等,
    5. 到了數理館還要分紅 不少的子類,
    6. 如 數學類的圖書、物理類的圖書、化學類的圖書,計算機類的圖書,
    7. 到了計算機類的圖書還要再分紅各類不一樣的子類,
    8. 如 按語言分類 c++、java、c#、php、python、javascript 等等,
    9. 如 按領域分類 網站編程、app 開發、遊戲開發、前端、後端等等,
    10. 每個子領域可能又要分紅不少領域,
    11. 一直到最後索引到一本一本的書,
    12. 這就是一個典型的樹結構。
    13. 還有 一個公司的組織架構也是這樣的一種樹結構,
    14. 從 CEO 開始下面可能有不一樣的部門,
    15. 如財務部門(Marketing Head)、人事部門(HR Head)、
    16. 技術部門(Finance Head)、市場部門(Audit Officer)等等,
    17. 每一個部門下面還有不一樣的職能分工,最後纔到具體的一個一我的。
    18. 還有家譜,他自己也是一個樹結構,
    19. 其實樹結構並不抽象,在生活中隨處可見。
  3. 樹結構很是的高效
    1. 好比文件管理,
    2. 不可能將全部的文件放到一個文件夾中,
    3. 而後用一個線性的結構進行存儲,
    4. 那樣的話查找文件太麻煩了,
    5. 可是若是給它作成樹機構的話,
    6. 那麼就能夠很容易的檢索到目標文件,
    7. 好比說我想檢索到個人照片,
    8. 直接找到我的文件夾,而後找到圖片文件夾,
    9. 最後找到本身的照片,這樣就很快速很高效的找到了目標文件。
    10. 在公司使用這種樹形的組織架構也是這個緣由,
    11. CEO 想就技術開發的一些問題進行一些討論,
    12. 他確定要找相應職能的一些人,
    13. 他不須要去市場部門、營銷部門、人事部門、財務部門、行政部門找人,
    14. 他直接去技術部這樣的開發部門去找人就行了,
    15. 一會兒就把查詢的範圍縮小了。
    16. 在數據結構領域設計樹結構的本質也是如此。
  4. 在計算機科學領域不少問題的處理
    1. 當你將數據使用樹結構進行存儲後,出奇的高效。
  5. 二分搜索樹(Binary Search Tree)
    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; // Element
       left; // Node
       right; // Node
    }
    複製代碼
  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. 例如我要找一本 JS 編程的書,
    11. 那麼進入圖書館我直接進入計算機科學這個區域找這本書,
    12. 其它的類的圖書我根本不用去管,
    13. 這也是樹這種結構存儲數據以後再對數據進行操做時
    14. 纔可以很是高效的核心緣由。
  9. 爲了可以達到二分搜索樹的性質
    1. 必須讓存儲的元素具備可比較性,
    2. 你要定義好 元素之間如何進行比較,
    3. 由於比較的方式是具備多種的,
    4. 必須保證元素之間能夠進行比較。
    5. 在鏈表和數組中則沒有這個要求,
    6. 這個就是二分搜索樹存儲數據的一個侷限性,
    7. 也說明了凡事都是有代價的,
    8. 若是想加快搜索的話就必須對數據有必定的要求。

代碼示例

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

    1. 因此應該對元素的類型有所限制,
    2. 這個限制就是 這個類型必須擁有可比較性,
    3. 也就是這個類型 element 必須具備可比較性。
  2. 代碼實現node

    class MyBinarySearchTreeNode {
       constructor(element, left, right) {
          // 實際存儲的元素
          this.element = element;
          // 當前節點的左子樹
          this.left = left;
          // 當前節點的右子樹
          this.right = right;
       }
    }
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 獲取二分搜索樹中節點個數
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索樹是否爲空的bool值
       isEmpty() {
          return this.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. 代碼python

    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 實際存儲的元素
          this.element = element;
          // 當前節點的左子樹
          this.left = left;
          // 當前節點的右子樹
          this.right = right;
       }
    }
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索樹中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          if (this.root === null) {
             this.root = new MyBinarySearchTreeNode(element);
             this.size++;
          } else this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索樹中 遞歸算法 -
       recursiveAdd(node, newElement) {
          // 解決最基本的問題 也就是遞歸函數調用的終止條件
          if (node === null) {
             node = new MyBinarySearchTreeNode(newElement);
             this.size++;
             return node;
          }
    
          // 1. 當前節點的元素比新元素大
          // 那麼新元素就會被添加到當前節點的左子樹去
          // 2. 當前節點的元素比新元素小
          // 那麼新元素就會被添加到當前節點的右子樹去
          // 3. 當前節點的元素比新元素相等
          // 什麼都不作了,由於目前不添加劇復的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 將複雜問題分解成多個性質相同的小問題,
          // 而後求出小問題的答案,
          // 最終構建出原問題的答案
          return node;
       }
    
       // 獲取二分搜索樹中節點個數 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索樹是否爲空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一個比較的方法,專門用來比較新增的元素大小 -
       // 第一個元素比第二個元素大 就返回 1
       // 第一個元素比第二個元素小 就返回 -1
       // 第一個元素比第二個元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接寫死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    }
    複製代碼
  2. 對於二分搜索的插入操做c++

    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. 代碼git

    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 實際存儲的元素
          this.element = element;
          // 當前節點的左子樹
          this.left = left;
          // 當前節點的右子樹
          this.right = right;
       }
    }
    
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索樹中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索樹中 遞歸算法 -
       recursiveAdd(node, newElement) {
          // 解決最基本的問題 也就是遞歸函數調用的終止條件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 當前節點的元素比新元素大
          // 那麼新元素就會被添加到當前節點的左子樹去
          // 2. 當前節點的元素比新元素小
          // 那麼新元素就會被添加到當前節點的右子樹去
          // 3. 當前節點的元素比新元素相等
          // 什麼都不作了,由於目前不添加劇復的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 將複雜問題分解成多個性質相同的小問題,
          // 而後求出小問題的答案,
          // 最終構建出原問題的答案
          return node;
       }
    
       // 獲取二分搜索樹中節點個數 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索樹是否爲空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一個比較的方法,專門用來比較新增的元素大小 -
       // 第一個元素比第二個元素大 就返回 1
       // 第一個元素比第二個元素小 就返回 -1
       // 第一個元素比第二個元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接寫死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    }
    複製代碼
  2. 雖然代碼量更少了,可是也更難理解的了一些github

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

二分搜索樹的查詢操做

  1. 查詢操做很是的容易
    1. 只須要不停的看每個 node 裏面存的元素,
    2. 不會牽扯到整個二分搜索樹的添加操做
  2. 和添加元素同樣須要使用遞歸的進行實現
    1. 在遞歸的過程當中就須要從二分搜索樹的根開始,
    2. 逐漸的轉移在二分搜索樹的子樹中縮小問題的規模,
    3. 縮小查詢的樹的規模,直到找到這個元素 e 或者發現找不到這個元素 e。
  3. 在數組和鏈表中有索引這個概念,
    1. 可是在二分搜索樹中沒有索引這個概念。

代碼示例

  1. 代碼

    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 實際存儲的元素
          this.element = element;
          // 當前節點的左子樹
          this.left = left;
          // 當前節點的右子樹
          this.right = right;
       }
    }
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索樹中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索樹中 遞歸算法 -
       recursiveAdd(node, newElement) {
          // 解決最基本的問題 也就是遞歸函數調用的終止條件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 當前節點的元素比新元素大
          // 那麼新元素就會被添加到當前節點的左子樹去
          // 2. 當前節點的元素比新元素小
          // 那麼新元素就會被添加到當前節點的右子樹去
          // 3. 當前節點的元素比新元素相等
          // 什麼都不作了,由於目前不添加劇復的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 將複雜問題分解成多個性質相同的小問題,
          // 而後求出小問題的答案,
          // 最終構建出原問題的答案
          return node;
       }
    
       // 判斷二分搜索樹中是否包含某個元素 +
       contains(element) {
          if (this.root === null) throw new Error("root is null. can't query.");
    
          return this.recursiveContains(this.root, element);
       }
    
       // 判斷二分搜索樹種是否包含某個元素 遞歸算法 -
       recursiveContains(node, element) {
          if (node === null) return false;
    
          // 當前節點元素比 要搜索的元素 大
          if (this.compare(node.element, element) > 0)
             return this.recursiveContains(node.left, element);
          else if (this.compare(node.element, element) < 0)
             // 當前元素比 要搜索的元素 小
             return this.recursiveContains(node.right, element);
          // 兩個元素相等
          else return true;
       }
    
       // 獲取二分搜索樹中節點個數 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索樹是否爲空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一個比較的方法,專門用來比較新增的元素大小 -
       // 第一個元素比第二個元素大 就返回 1
       // 第一個元素比第二個元素小 就返回 -1
       // 第一個元素比第二個元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接寫死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    }
    複製代碼

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

  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

    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 實際存儲的元素
          this.element = element;
          // 當前節點的左子樹
          this.left = left;
          // 當前節點的右子樹
          this.right = right;
       }
    }
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索樹中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索樹中 遞歸算法 -
       recursiveAdd(node, newElement) {
          // 解決最基本的問題 也就是遞歸函數調用的終止條件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 當前節點的元素比新元素大
          // 那麼新元素就會被添加到當前節點的左子樹去
          // 2. 當前節點的元素比新元素小
          // 那麼新元素就會被添加到當前節點的右子樹去
          // 3. 當前節點的元素比新元素相等
          // 什麼都不作了,由於目前不添加劇復的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 將複雜問題分解成多個性質相同的小問題,
          // 而後求出小問題的答案,
          // 最終構建出原問題的答案
          return node;
       }
    
       // 判斷二分搜索樹中是否包含某個元素 +
       contains(element) {
          if (this.root === null) throw new Error("root is null. can't query.");
    
          return this.recursiveContains(this.root, element);
       }
    
       // 判斷二分搜索樹種是否包含某個元素 遞歸算法 -
       recursiveContains(node, element) {
          if (node === null) return false;
    
          // 當前節點元素比 要搜索的元素 大
          if (this.compare(node.element, element) > 0)
             return this.recursiveContains(node.left, element);
          else if (this.compare(node.element, element) < 0)
             // 當前元素比 要搜索的元素 小
             return this.recursiveContains(node.right, element);
          // 兩個元素相等
          else return true;
       }
    
       // 前序遍歷 +
       preOrder(operator) {
          this.recursivePreOrder(this.root, operator);
       }
    
       // 前序遍歷 遞歸算法 -
       recursivePreOrder(node, operator) {
          if (node === null) return;
    
          // 調用一下操做方法
          operator(node.element);
          console.log(node, node.element);
    
          // 繼續遞歸遍歷左右子樹
          this.recursivePreOrder(node.left, operator);
          this.recursivePreOrder(node.right, operator);
       }
    
       // 獲取二分搜索樹中節點個數 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索樹是否爲空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一個比較的方法,專門用來比較新增的元素大小 -
       // 第一個元素比第二個元素大 就返回 1
       // 第一個元素比第二個元素小 就返回 -1
       // 第一個元素比第二個元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接寫死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    }
    複製代碼
  2. Main

    class Main {
       constructor() {
          this.alterLine('MyBinarySearchTree Area');
          let myBinarySearchTree = new MyBinarySearchTree();
          let nums = [5, 3, 6, 8, 4, 2];
          for (var i = 0; i < nums.length; i++) {
             myBinarySearchTree.add(nums[i]);
          }
    
          /////////////////
          // 5 //
          // / \ //
          // 3 6 //
          // / \ \ //
          // 2 4 8 //
          /////////////////
          myBinarySearchTree.preOrder(this.show);
    
          this.show(myBinarySearchTree.contains(1));
          console.log(myBinarySearchTree.contains(1));
       }
    
       // 將內容顯示在頁面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展現分割線
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    window.onload = function() {
       // 執行主函數
       new Main();
    };
    複製代碼

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

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

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

  1. MyBinarySearchTree

    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 實際存儲的元素
          this.element = element;
          // 當前節點的左子樹
          this.left = left;
          // 當前節點的右子樹
          this.right = right;
       }
    }
    
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索樹中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索樹中 遞歸算法 -
       recursiveAdd(node, newElement) {
          // 解決最基本的問題 也就是遞歸函數調用的終止條件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 當前節點的元素比新元素大
          // 那麼新元素就會被添加到當前節點的左子樹去
          // 2. 當前節點的元素比新元素小
          // 那麼新元素就會被添加到當前節點的右子樹去
          // 3. 當前節點的元素比新元素相等
          // 什麼都不作了,由於目前不添加劇復的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 將複雜問題分解成多個性質相同的小問題,
          // 而後求出小問題的答案,
          // 最終構建出原問題的答案
          return node;
       }
    
       // 判斷二分搜索樹中是否包含某個元素 +
       contains(element) {
          if (this.root === null) throw new Error("root is null. can't query.");
    
          return this.recursiveContains(this.root, element);
       }
    
       // 判斷二分搜索樹種是否包含某個元素 遞歸算法 -
       recursiveContains(node, element) {
          if (node === null) return false;
    
          // 當前節點元素比 要搜索的元素 大
          if (this.compare(node.element, element) > 0)
             return this.recursiveContains(node.left, element);
          else if (this.compare(node.element, element) < 0)
             // 當前元素比 要搜索的元素 小
             return this.recursiveContains(node.right, element);
          // 兩個元素相等
          else return true;
       }
    
       // 前序遍歷 +
       preOrder(operator) {
          this.recursivePreOrder(this.root, operator);
       }
    
       // 前序遍歷 遞歸算法 -
       recursivePreOrder(node, operator) {
          if (node === null) return;
    
          // 調用一下操做方法
          operator(node.element);
          console.log(node, node.element);
    
          // 繼續遞歸遍歷左右子樹
          this.recursivePreOrder(node.left, operator);
          this.recursivePreOrder(node.right, operator);
       }
    
       // 獲取二分搜索樹中節點個數 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索樹是否爲空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一個比較的方法,專門用來比較新增的元素大小 -
       // 第一個元素比第二個元素大 就返回 1
       // 第一個元素比第二個元素小 就返回 -1
       // 第一個元素比第二個元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接寫死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 輸出二分搜索樹中的信息
       // @Override toString 2018-11-03-jwl
       toString() {
          let treeInfo = '';
          treeInfo += this.getBinarySearchTreeString(this.root, 0, treeInfo);
          return treeInfo;
       }
    
       // 寫一個輔助函數,用來生成二分搜索樹信息的字符串
       getBinarySearchTreeString(node, depth, treeInfo, pageContent = '') {
          //之前序遍歷的方式
    
          if (node === null) {
             treeInfo += this.getDepthString(depth) + 'null \r\n';
    
             pageContent = this.getDepthString(depth) + 'null<br /><br />';
             document.body.innerHTML += `${pageContent}`;
    
             return treeInfo;
          }
    
          treeInfo += this.getDepthString(depth) + node.element + '\r\n';
    
          pageContent =
             this.getDepthString(depth) + node.element + '<br /><br />';
          document.body.innerHTML += `${pageContent}`;
    
          treeInfo = this.getBinarySearchTreeString(
             node.left,
             depth + 1,
             treeInfo
          );
          treeInfo = this.getBinarySearchTreeString(
             node.right,
             depth + 1,
             treeInfo
          );
    
          return treeInfo;
       }
    
       // 寫一個輔助函數,用來生成遞歸深度字符串
       getDepthString(depth) {
          let depthString = '';
          for (var i = 0; i < depth; i++) {
             depthString += '-- ';
          }
          return depthString;
       }
    }
    複製代碼
  2. Main

    class Main {
       constructor() {
          this.alterLine('MyBinarySearchTree Area');
          let myBinarySearchTree = new MyBinarySearchTree();
          let nums = [5, 3, 6, 8, 4, 2];
          for (var i = 0; i < nums.length; i++) {
             myBinarySearchTree.add(nums[i]);
          }
    
          /////////////////
          // 5 //
          // / \ //
          // 3 6 //
          // / \ \ //
          // 2 4 8 //
          /////////////////
    
          console.log(myBinarySearchTree.toString());
       }
    
       // 將內容顯示在頁面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展現分割線
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    window.onload = function() {
       // 執行主函數
       new Main();
    };
    複製代碼

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

  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#JS這樣的語言都有垃圾回收機制,
    9. 因此不須要你對內存管理進行手動的控制,
    10. c++ 語言中須要手動的控制內存,
    11. 那麼在二分搜索樹內存釋放這方面就須要使用後序遍歷。
    12. 對於一些樹結構的問題,
    13. 不少時候也是須要先針對一個節點的孩子節點求解出答案,
    14. 最終再由這些答案組合成針對這個節點的答案,
    15. 樹形問題有分治算法、回溯算法、動態規劃算法等等。
  6. 二分搜索樹的前中後序遍歷

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

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

  1. MyBinarySearchTree

    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 實際存儲的元素
          this.element = element;
          // 當前節點的左子樹
          this.left = left;
          // 當前節點的右子樹
          this.right = right;
       }
    }
    
    // 自定義二分搜索樹
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索樹中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索樹中 遞歸算法 -
       recursiveAdd(node, newElement) {
          // 解決最基本的問題 也就是遞歸函數調用的終止條件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 當前節點的元素比新元素大
          // 那麼新元素就會被添加到當前節點的左子樹去
          // 2. 當前節點的元素比新元素小
          // 那麼新元素就會被添加到當前節點的右子樹去
          // 3. 當前節點的元素比新元素相等
          // 什麼都不作了,由於目前不添加劇復的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 將複雜問題分解成多個性質相同的小問題,
          // 而後求出小問題的答案,
          // 最終構建出原問題的答案
          return node;
       }
    
       // 判斷二分搜索樹中是否包含某個元素 +
       contains(element) {
          if (this.root === null) throw new Error("root is null. can't query.");
    
          return this.recursiveContains(this.root, element);
       }
    
       // 判斷二分搜索樹種是否包含某個元素 遞歸算法 -
       recursiveContains(node, element) {
          if (node === null) return false;
    
          // 當前節點元素比 要搜索的元素 大
          if (this.compare(node.element, element) > 0)
             return this.recursiveContains(node.left, element);
          else if (this.compare(node.element, element) < 0)
             // 當前元素比 要搜索的元素 小
             return this.recursiveContains(node.right, element);
          // 兩個元素相等
          else return true;
       }
    
       // 前序遍歷 +
       preOrder(operator) {
          this.recursivePreOrder(this.root, operator);
       }
    
       // 前序遍歷 遞歸算法 -
       recursivePreOrder(node, operator) {
          if (node === null) return;
    
          // 調用一下操做方法
          operator(node.element);
          console.log(node, node.element);
    
          // 繼續遞歸遍歷左右子樹
          this.recursivePreOrder(node.left, operator);
          this.recursivePreOrder(node.right, operator);
       }
    
       // 中序遍歷 +
       inOrder(operator) {
          this.recursiveInOrder(this.root, operator);
       }
    
       // 中序遍歷 遞歸算法 -
       recursiveInOrder(node, operator) {
          if (node == null) return;
    
          this.recursiveInOrder(node.left, operator);
    
          operator(node.element);
          console.log(node.element);
    
          this.recursiveInOrder(node.right, operator);
       }
    
       // 後序遍歷 +
       postOrder(operator) {
          this.recursivePostOrder(this.root, operator);
       }
    
       // 後序遍歷 遞歸算法 -
       recursivePostOrder(node, operator) {
          if (node == null) return;
    
          this.recursivePostOrder(node.left, operator);
          this.recursivePostOrder(node.right, operator);
    
          operator(node.element);
          console.log(node.element);
       }
    
       // 獲取二分搜索樹中節點個數 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索樹是否爲空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一個比較的方法,專門用來比較新增的元素大小 -
       // 第一個元素比第二個元素大 就返回 1
       // 第一個元素比第二個元素小 就返回 -1
       // 第一個元素比第二個元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接寫死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 輸出二分搜索樹中的信息
       // @Override toString 2018-11-03-jwl
       toString() {
          let treeInfo = '';
          treeInfo += this.getBinarySearchTreeString(this.root, 0, treeInfo);
          return treeInfo;
       }
    
       // 寫一個輔助函數,用來生成二分搜索樹信息的字符串
       getBinarySearchTreeString(node, depth, treeInfo, pageContent = '') {
          //之前序遍歷的方式
    
          if (node === null) {
             treeInfo += this.getDepthString(depth) + 'null \r\n';
    
             pageContent = this.getDepthString(depth) + 'null<br /><br />';
             document.body.innerHTML += `${pageContent}`;
    
             return treeInfo;
          }
    
          treeInfo += this.getDepthString(depth) + node.element + '\r\n';
    
          pageContent =
             this.getDepthString(depth) + node.element + '<br /><br />';
          document.body.innerHTML += `${pageContent}`;
    
          treeInfo = this.getBinarySearchTreeString(
             node.left,
             depth + 1,
             treeInfo
          );
          treeInfo = this.getBinarySearchTreeString(
             node.right,
             depth + 1,
             treeInfo
          );
    
          return treeInfo;
       }
    
       // 寫一個輔助函數,用來生成遞歸深度字符串
       getDepthString(depth) {
          let depthString = '';
          for (var i = 0; i < depth; i++) {
             depthString += '-- ';
          }
          return depthString;
       }
    }
    複製代碼
  2. Main

    // main 函數
    class Main {
       constructor() {
          this.alterLine('MyBinarySearchTree Area');
          let myBinarySearchTree = new MyBinarySearchTree();
          let nums = [5, 3, 6, 8, 4, 2];
          for (var i = 0; i < nums.length; i++) {
             myBinarySearchTree.add(nums[i]);
          }
    
          /////////////////
          // 5 //
          // / \ //
          // 3 6 //
          // / \ \ //
          // 2 4 8 //
          /////////////////
    
          this.alterLine('MyBinarySearchTree PreOrder Area');
          myBinarySearchTree.preOrder(this.show);
    
          this.alterLine('MyBinarySearchTree InOrder Area');
          myBinarySearchTree.inOrder(this.show);
    
          this.alterLine('MyBinarySearchTree PostOrder Area');
          myBinarySearchTree.postOrder(this.show);
       }
    
       // 將內容顯示在頁面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展現分割線
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    
    // 頁面加載完畢
    window.onload = function() {
       // 執行主函數
       new Main();
    };
    複製代碼

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

  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. 後序遍歷訪問節點都是在第三個訪問機會的位置纔去訪問節點,
相關文章
相關標籤/搜索