前端學習資料結構1 二分排序樹(BST)

Ace7523發表於2019-02-06

前端學習資料結構1 二分排序樹(BST)
對於我這樣一個畢業了一年半左右的前端來說,很摸著良心的講,平時工作中,沒有遇到過寫什麼樹。但是有大塊時間的時候,覺得這些還是得好好複習一下,或者說預習。今天的主角是二分搜尋樹。

二分搜尋樹

二分搜尋樹也是二叉樹,和二叉樹長的一樣,就是有個特點,每個節點的值比他的左子樹的值大,比他的右子樹的值小。如下圖所示:

前端學習資料結構1 二分排序樹(BST)
那麼接下來就來實現一下這個樹:

// 宣告節點建構函式 當前節點的值,左節點,右節點
class Node {
  constructor(value) {
    this.value = value
    this.left = null
    this.right = null
  }
}
// 二分搜尋樹建構函式
class BST {
  constructor() {
    this.root = null
    this.size = 0
  }
  getSize() {
    return this.size
  }
  isEmpty() {
    return this.size === 0
  }
  addNode(v) {
    // 每次新增子節點後,更新樹
    this.root = this._addChild(this.root, v)
  }
  _addChild(node, v) {
    if (!node) {
      this.size++
      return new Node(v)
    }
    if (node.value > v) {
      console.log(`在${node.value}節點,新增左節點${v}`)
      node.left = this._addChild(node.left, v)
    } else if (node.value < v) {
      console.log(`在${node.value}節點,新增右節點${v}`)
      node.right = this._addChild(node.right, v)
    }
    return node
  }    
}

let bst = new BST()
// 第一個節點
bst.addNode(10)
// 後續節點
bst.addNode(8)
bst.addNode(6)
bst.addNode(3)
bst.addNode(7)
bst.addNode(9)
bst.addNode(12)
bst.addNode(11)
bst.addNode(15)
bst.addNode(14)

console.log(bst)
複製程式碼

執行結果如下:

前端學習資料結構1 二分排序樹(BST)

左節點值是8 右節點值12 中間節點值是10。

再展開左節點看一下

前端學習資料結構1 二分排序樹(BST)

右邊就不再展開贅述了

二分搜尋樹--遍歷

二叉樹遍歷 分為深度遍歷(先序遍歷、中序遍歷、後序遍歷,三種遍歷的區別在於何時訪問節點), 廣度遍歷(一層層地遍歷)

先序遍歷

繞不開的遞迴又出現了,想起有一天右邊同事妹子很開心的和我說她知道了遞迴的終極奧義:“遞迴的終極奧義就是:不要想遞迴是怎麼具體一步步實現的”

那我先來實現一下先序遍歷

class BST { 
    ...
    // 新增先序遍歷實現,其實就是很簡單的幾行程式碼
    preTraversal() {
      this._pre(this.root)
    }
    _pre(node) {
      if (node) {
        // 訪問節點的值
        console.log(node.value)
        // 遞迴左右子樹
        this._pre(node.left)
        this._pre(node.right)
      }
    }
}
// 用上面生成的bst例項執行一下,結果如下圖
bst.preTraversal()
複製程式碼

前端學習資料結構1 二分排序樹(BST)

那麼這個結果是如何生成的呢?

  1. 先是列印10,這個毫無爭議 然後 this._pre(node.left),this._pre(node.right)這兩個方法看似兩行,其實左子樹沒有遍歷完結的話是不會去遍歷右子樹的
  2. 此時this._pre(node.left)中引數是如下圖部分,同樣,會對這部分執行那3行程式碼,首先會列印8 , 然後以8那個節點作為根節點,去遍歷左右子樹

前端學習資料結構1 二分排序樹(BST)
3. 列印8之後,遍歷左子樹的引數如下圖部分,同樣,會對這部分執行那3行程式碼,首先會列印6 , 然後以6那個節點作為根節點,去遍歷左右子樹

前端學習資料結構1 二分排序樹(BST)
4. 這個時候遍歷左子樹時候 this_pre(node.left)的引數只是3節點,3節點沒有子樹,那麼在執行上面那3行程式碼只是列印3 ,this._pre(node.left)和this._pre(node.right)執行不下去了。

  1. 上面第三步驟時候,列印6之後,先遍歷左子樹,後遍歷右子樹。而此時的遍歷左子樹只是列印3。於是要去遍歷6的右子樹,也就是列印7。
  2. 列印7之後,本例中,作為節點8的左節點已經遍歷完畢。遍歷8的右節點,也就是列印9,之後8的右節點也遍歷完畢。
  3. 再往回退,列印9之後,也就是10節點的左節點已經全部遍歷完畢。 所以列印的結果是 10 8 6 3 7 9
  4. 同樣的邏輯此時該去遍歷10節點的右節點了。依次列印12 11 15 14 ,所以最終結果就是 10 8 6 3 7 9 12 11 15 14

一步步的推導遞迴的具體實現後,還真的覺的上面所說遞迴的奧義那句話總結的是很有意思的。

中序遍歷

class BST { 
    ...
    // 新增中序遍歷實現,其實就是很簡單的幾行程式碼
    midTraversal() {
      this._mid(this.root)
    }
    _mid(node) {
      if (node) {
        // 1語句 後面講解時候說'1語句'就指代下面這句
        this._mid(node.left)
        // 2語句
        console.log(node.value)
        // 3語句
        this._mid(node.right)
      }
    }
}
// 用上面生成的bst例項執行一下,結果如下圖
bst.midTraversal()
複製程式碼

前端學習資料結構1 二分排序樹(BST)

  1. 上面的逐條分析之後,中序遍歷就很容易理解了。上面的迴圈體主要三條語句,在第一次執行 2語句之前,我們要想一下,此時的引數node,是什麼?換個問法,在引數node是樹中的哪個節點的時候,才會第一次的執行2語句? 我來截圖吧

前端學習資料結構1 二分排序樹(BST)
只有當node是這個3節點的時候,才會第一次觸發這個console.log(node.value),而此時此刻3語句也相當於是一條廢語句。

  1. 現在回想一下,當node引數是這個3節點的時候,回退一下,他的上一步執行函式中node是什麼?我來截圖

前端學習資料結構1 二分排序樹(BST)
node是圖中這個6節點的時候,1語句執行完畢,才會執行此時此刻的console.log(node.value),也就是列印出了6,然後執行3語句 ,以7節點作為node引數,再去執行123語句,此時此刻13語句都是廢語句,也就是列印出7 。也就是列印 3 6 7

  1. 那麼再回退一下,列印出7之後,也就是node.left 是6這個節點的遞迴執行完畢了,此時此刻的node是8,然後執行console.log(node.value),再去執行3語句,此時此刻的3語句的引數是node.left,也就是9節點,對這個9節點,依次執行123語句,9節點沒有子節點,所以只是列印出9 ,至此,整個8節點遍歷完畢。

  2. 列印出了9之後, 8節點完全遍歷完畢了,8節點作為node.left,那麼此時的node是10節點,執行2語句,列印出10 ,那麼後續的,相信不用我再說了吧

  3. 這就是為什麼二分排序樹的中序遍歷的結果是排序好的。

後序遍歷

class BST { 
    ...
    // 新增後序遍歷實現,其實就是很簡單的幾行程式碼
    backTraversal() {
      this._back(this.root)
    }
    _back(node) {
      if (node) {
        // 1語句 後面講解時候說'1語句'就指代下面這句
        this._back(node.left)
        // 2語句
        this._back(node.right)
        // 3語句
        console.log(node.value)
      }
    }
}
// 用上面生成的bst例項執行一下,結果如下圖
bst.backTraversal()
複製程式碼

前端學習資料結構1 二分排序樹(BST)

  1. 遞迴的奧義:就是不要想遞迴是怎麼一步步具體實現的。
  2. 後續遍歷與之前遍歷方式的區別是的3語句是列印,那麼能否根據上面的前序推導和中序推導加上奧義,直接看著樹的圖,寫出後續遍歷的結果呢?

廣度遍歷

未完待續

(以上參考掘金小冊,融入自己的實操和思考,因為是收費小冊,參考的地址原文沒辦法貼出來,yck老師很贊,建議大家都去看看他的小冊)

前端學習資料結構1 二分排序樹(BST)

前端學習資料結構1 二分排序樹(BST)

相關文章