演算法篇 - 二叉樹
在前端的工作當中,二叉樹不怎麼常見,雖然沒有快排、冒泡、去重、二分、希爾等演算法常見,但是它的作用,在某些特定的場景下,是非常重要的。
目前es6的使用場景比較多,所以我準備能用es6的地方就用es6去實現。 複製程式碼
正文

上圖是我從網上找的,最主要是讓大家看一下,樹長啥樣。
在這裡簡單的介紹一下有關二叉樹的術語,在後續討論中將會提到。一棵樹最上面的節點稱為 根節點,如果一個節點下面連線兩個節點,那麼該節點稱為父節點,它下面的節點稱為子 節點。一個節點最多隻有 0 - 2 個子節點。沒有任何子節點的節點稱為葉子節點。
什麼是二叉樹
二叉樹就是一種非線性的資料結構,一般是用來儲存具有層級關係的資料,比如,我們要做一個視覺化的檔案系統,類似於雲盤網頁版,它有區分不同的資料夾,每個資料夾下面都有不同的內容,它們每個資料夾,是沒有任何的關係的,唯一的關係,就是同一層級的資料夾,都有一個父級資料夾。
和非線性資料結構相反的,是線性資料結構,線性資料結構其實在平時就比較常見了,前端常用的線性資料結構有:棧、佇列、陣列。
為什麼要用二叉樹
選擇二叉樹而不是那些基本的資料結構,是因為在二叉樹上進行查詢非常快,為二叉樹新增或刪除元素 也非常快。
二叉樹特點
- 二叉樹的每個節點的子節點不允許超過兩個;
- 每個節點的左節點永遠都比自己小,右節點反之;
二叉樹的實現
二叉樹的實現功能包括 新增節點 、 刪除節點 、 查詢節點(最大值、最小值,某一個指定值) ;
二叉樹的遍歷方式包括 中序遍歷 、 先序遍歷 、 後序遍歷 ;
建立二叉樹節點和節點的操作類
建立一個節點
在建立一個節點時,我們要記住二叉樹節點的特性,子節點不允許超過兩個:
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 方法,顯示當前節點的資料;
建立一個操作節點的類
現在,我們有了一個節點,知道了這個節點有哪些屬性,接下來,我們建立一個可以操作這些節點的類:
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 原始碼解析(例項化前)的最終章的,但是涉及到的東西比較多,而且我想把前幾章的總結一下寫到最後一章。
所以這一章就先寫一章有關演算法的文章,謝謝大家支援:pray: