算法篇 - 二叉搜索樹

前言

在前端的工做當中,二叉搜索樹不怎麼常見,雖然沒有快排、冒泡、去重、二分、希爾等算法常見,可是它的做用,在某些特定的場景下,是很是重要的。javascript

目前es6的使用場景比較多,因此我準備能用es6的地方就用es6去實現。
複製代碼

正文

上圖是我從網上找的,最主要是讓你們看一下,樹長啥樣。前端

在這裏簡單的介紹一下有關二叉搜索樹的術語,在後續討論中將會提到。一棵樹最上面的節點稱爲 根節點,若是一個節點下面鏈接兩個節點,那麼該節點稱爲父節點,它下面的節點稱爲子 節點。一個節點最多隻有 0 - 2 個子節點。沒有任何子節點的節點稱爲葉子節點。vue

什麼是二叉搜索樹

二叉搜索樹就是一種非線性的數據結構,通常是用來存儲具備層級關係的數據,好比,咱們要作一個可視化的文件系統,相似於雲盤網頁版,它有區分不一樣的文件夾,每一個文件夾下面都有不一樣的內容,它們每一個文件夾,是沒有任何的關係的,惟一的關係,就是同一層級的文件夾,都有一個父級文件夾。java

和非線性數據結構相反的,是線性數據結構,線性數據結構其實在平時就比較常見了,前端經常使用的線性數據結構有:棧、隊列、數組。node

爲何要用二叉搜索樹

選擇二叉搜索樹而不是那些基本的數據結構,是由於在二叉搜索樹上進行查找很是快,爲二叉搜索樹添加或刪除元素 也很是快。es6

二叉搜索樹搜索特色

  • 二叉搜索樹的每一個節點的子節點不容許超過兩個;
  • 每一個節點的左節點永遠都比本身小,右節點反之;

二叉搜索樹的實現

二叉搜索樹的實現功能包括添加節點刪除節點查詢節點(最大值、最小值,某一個指定值)算法

二叉搜索樹的遍歷方式包括中序遍歷先序遍歷後序遍歷數組

建立二叉搜索樹節點和節點的操做類

建立一個節點

在建立一個節點時,咱們要記住二叉搜索樹節點的特性,子節點不容許超過兩個:數據結構

class Node {
    constructor({data = null, left = null, right = null}){
        this._data = data
        this._left = left
        this._right = right
    }
    show(){
        return this._data
    }
}
複製代碼

這樣,建立了一個節點,默認爲 null,有 left 和 right 兩個節點,添加一個 show 方法,顯示當前節點的數據;post

建立一個操做節點的類

如今,咱們有了一個節點,知道了這個節點有哪些屬性,接下來,咱們建立一個能夠操做這些節點的類:

class BinarySearchTree {
    constructor(){
        this._root = null
    }
}
複製代碼

這裏咱們建立了一個根節點,這個根節點就是二叉搜索樹最底層的那個根,接下來,咱們先添加一個添加節點的方法。

添加節點

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  insert(data) {
    let node = new Node({
      data: data
    })
    if (this._root == null) {
      this._root = node
    }
    else { 
      let current, parent
      current = this._root
      while (true) { 
        parent = current
        if (data < current.data) {
          current = current.left
          if (current == null) {
            parent.left = node
            break
          }
        }
        else { 
          current = current.right
          if (current == null) { 
            parent.right = node
            break
          }
        }
      }
    }
  }
}
複製代碼

這裏,咱們添加了一個 insert 方法,這個方法就是用來添加節點的;

初始化先檢查根節點 root 是不是 null,若是是 null,那說明不存在根節點,以當前添加的節點爲根節點;

若是存在根節點的話,那麼接下來值的對比目標,初始化以根節點作對比目標,確認大於根節點,仍是小於根節點;

若是小於的話,那麼就下次用根節點的左節點 left 來作對比,固然,若是左節點是空的,直接把當前要添加的節點看成左節點 left 就ok了。

若是大於的話,和小於反之;

而後咱們如今,開始添加值,測試一些方法:

let bst = new BinarySearchTree()
bst.insert(2)
bst.insert(1)
bst.insert(3)
複製代碼

咱們添加了一個根節點,是2,而後添加了兩個葉子的節點 1 和 3 ,1 就在根節點 2 的左側, 3 就在根節點 2 的右側,接下來咱們加一個刪除節點的方法。

刪除節點

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  remove(data) {
    this._root = this._removeNode(this._root, data)
  }
  _removeNode(node, data) {
    if (node == null) {
      return null
    }
    if (data == node._data) {
      if (node._left == null && node._right == null) {
        return null
      }
      if (node._left == null) {
        return node._right
      }
      if (node._right == null) {
        return node._left
      }
      var tempNode = this._getSmallest(node._right)
      node._data = tempNode._data
      node._right = this._removeNode(node._right, tempNode._data)
      return node
    } else if (data < node._data) {
      node._left = this._removeNode(node._left, data)
      return node
    } else {
      node._right = this._removeNode(node._right, data)
      return node
    }
  }
  _getSmallest(node) {
    if (node._left == null) {
      return node
    } else {
      return this._getSmallest(node._left)
    }
  }
}
複製代碼

這個是用來刪除某一個節點的方法。

這個方法最後返回的是一個新的 node 節點,若是第一次調用的時候 node 是 null 的話,說明這個二叉搜索樹是空的,不存在任何值,直接返回空;

若是當前要刪除的值,是當前節點的值,那麼去檢查它的左右節點是否存在值,若是都不存在,直接返回 null,由於說明當前的節點下面沒有子級節點,就不須要對子級節點作處理了,直接刪除當前節點就好;

若是左節點是 null 的,那麼直接用當前節點的右節點替換當前節點;

反之,右節點是 null 的,那麼直接用當前節點的左節點替換當前節點;

若是,當前要刪除的節點,左右節點都存在的話,那麼就去遍歷要刪除節點的右側節點,若是右側節點不存在它的左節點,那麼直接返回右側節點,若是存在,一直遞歸,找到最底層的一個左側節點返回結果;

這裏其實簡要歸納一下,就是去找刪除節點右節點樹當中的最小值,來代替當前被刪除節點的位置
複製代碼

若是當前要刪除的值,比當前節點的值小,就遞歸調用,一直找到當前值並刪除爲止;

若是當前要刪除的值,比當前節點的值大,與上面反之;

接下來,咱們添加一個查找的方法:

查找當前節點

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  find(data) {
    let current = this._root
    while (current != null) {
      if (current._data == data) {
        return true
      } else if (data < current._data) {
        current = current._left
      } else {
        current = current._right
      }
    }
    return false
  }
}
複製代碼

這個方法是用來查找一個值的,若是當前查詢的值小於當前節點的值,那就從當前的節點左側去查詢;

反之,若是大於,就直接去右側查詢;

若是這個值始終查詢不到,那麼就返回 false,不然就是 true。

查找最小值

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  getMin() {
    let current = this._root
    while (current._left != null) {
      current = current._left
    }
    return current._data
  }
}
複製代碼

這是一個查詢最小值的方法,因爲二叉搜索樹數據結構的特殊性,左側的值永遠是最小的,因此一直查詢到低,找到最底層的左節點返回就能夠了。

查詢最大值

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  getMax() {
    let current = this._root
    while (current._right != null) {
      current = current._right
    }
    return current._data
  }
}
複製代碼

和查詢最小值相反。

操做二叉搜索樹節點的方式都有了,接下來該遍歷二叉搜索樹了。

遍歷二叉搜索樹

把二叉搜索樹的數據遍歷一遍,用中序先序後序遍歷,代碼量比較少,代碼也不是僞代碼都是我本身測過的,結果就不截圖了,把執行順序的流程圖發一下,結果你們感興趣本身跑一下就行了:

中序遍歷

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  inOrder(node) {
    if (node != null) {
      this.inOrder(node._left)
      console.log(node.show())
      this.inOrder(node._right)
    }
  }
}
複製代碼

先遍歷左節點,在遍歷根節點,最後遍歷右節點,步驟以下:

先序遍歷

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  preOrder(node) {
    if (node != null) {
      console.log(node.show())
      this.inOrder(node._left)
      this.inOrder(node._right)
    }
  }
}
複製代碼

這是先序遍歷,先遍歷根節點,在遍歷左節點,最後遍歷右節點,步驟以下:

後序遍歷

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  postOrder(node) {
    if (node != null) {
      this.inOrder(node._left)
      this.inOrder(node._right)
      console.log(node.show())
    }
  }
}
複製代碼

這是後序遍歷,先遍歷葉子節點,從左到右,最後遍歷根節點,步驟以下:

結束語

到這裏,二叉搜索樹講解的就差很少了,你們對於哪裏有疑問,隨時歡迎評論。

原本這一章是應該寫 vue 源碼解析(實例化前)的最終章的,可是涉及到的東西比較多,並且我想把前幾章的總結一下寫到最後一章。

因此這一章就先寫一章有關算法的文章,謝謝你們支持🙏

相關文章
相關標籤/搜索