有段時間沒有寫文章了,整我的感受變得有點懶散了,你們可不要向我同樣哦~
今天開始 seaconch 打算開始記錄 JavaScript 數據結構的學習經歷。
至此,開始。node
課本:《學習JavaScript數據結構與算法 (第2版)》
術語:算法
BST 有以下特性:數據結構
爲了存儲 BST,咱們先定義一個 Node 類型,存儲各個節點。ide
Node 節點的構造函數默認爲其初始化 Subtree 都是 null。函數
/** * 節點 */ class Node { constructor (key) { this.key = key; this.left = null; this.right = null; } }
而後是 BSTpost
BST 的類型咱們只初始化了一個根節點 root。學習
/** * 二叉排序樹 */ class BinarySearchTree { constructor() { this.root = null; } }
插入節點只要遵循一個原則就好:大與 node.key 就向 node.right 中插入,小於 node.key 就向 node.left 插入。this
首先定義私有函數。spa
const insertNode = Symbol('insertNode');
/** * 插入節點 */ insert(key) { let newNode = new Node(key); if (this.root === null) this.root = newNode; else this[insertNode](this.root, newNode); } /** * 插入節點 * @param {當前節點} node * @param {新節點} newNode */ [insertNode] (node, newNode) { if (newNode.key < node.key) { if (node.left === null) node.left = newNode; else this[insertNode](node.left, newNode); } else { if (node.right === null) node.right = newNode; else this[insertNode](node.right, newNode); } }
這裏稍微的說明一下,之因此寫成私有函數,無非就是爲了避免但願外部看到這些不必的。code
其實東西多了感受也會亂糟糟的...
接下來爲了查看一下效果,咱們來寫一個初始化 BST 的函數。
咱們的目標是初始化一個這樣的 BST。
/** * 初始化數據 * @param {樹} tree */ function initialization(tree) { let treeKeys = [50, 25, 13, 5, 19, 35, 28, 49, 75, 64, 56, 74, 85, 79, 99]; treeKeys.forEach( key => tree.insert(key) ) }
如今來試試實例化一個 BST 來看看效果。
let tree = new BinarySearchTree(); initialization(tree); console.log(tree.root);
打印效果如圖
由:
- 若 LST 不爲空,則 LST 全部節點值都 小 於它的根節點值
- 若 RST 不爲空,則 RST 全部節點值都 大 於它的根節點值
- 左右子樹也都是 BST
所以咱們能夠相對快速的查找元素。
定義私有函數
const searchNode = Symbol('searchNode');
/** * 是否存在目標 key */ existKey(key) { return this[searchNode](this.root, key); } /** * * @param {當前節點} node * @param {key} key */ [searchNode] (node, key) { if (node === null) return false; if (key < node.key) return this[searchNode](node.left, key); else if (key > node.key) return this[searchNode](node.right, key); else return true; }
咱們的思路是這樣的:
若是要查找的 key值 小於 當前節點的 key值,則向 LST 繼續查找;
若是要查找的 key值 大於 當前節點的 key值,則向 RST 繼續查找;
若是要查找的 key值 等於 當前節點的 key值,則返回 true
運行效果以下:
由:
- 若 RST 不爲空,則 RST 全部節點值都 大 於它的根節點值
- 左右子樹也都是 BST
咱們能夠求得最大值
很明顯是這樣的
代碼以下
定義私有函數
const maxNode = Symbol('maxNode');
/** * 獲取最大節點 key */ max() { return this[maxNode](this.root); } /** * 獲取最大節點 key * @param {節點} node */ [maxNode] (node) { if (node) { while (node && node.right !== null) { node = node.right; } return node.key; } return null; }
輸出結果爲:99
獲取最小值的方法與最大值相似,只是方向變了一下
定義私有函數
const minNode = Symbol('minNode');
/** * 獲取最小節點 key */ min() { return this[minNode](this.root); } /** * 獲取最小節點 key * @param {節點} node */ [minNode] (node) { if (node) { while (node && node.left !== null) { node = node.left; } return node.key; } return null; }
運行結果天然是:5
移除相對來講複雜一點,由於假如咱們要移除的是一個父節點,那他們的子節點怎麼辦?
固然也是有相應的應對措施的。
對於存在 subtree 的 node 來講,就要考慮全部狀況分別處理
圖例說明:
1. LST 等於 null
2. RST 等於 null
3. LST 和 RST 都不等於 null
定義私有函數
const removeNode = Symbol('removeNode'); const findMinNode = Symbol('findMinNode');
/** * 刪除節點 */ remove(key) { this[removeNode](this.root, key); } /** * 刪除節點,返回刪除後的 tree * @param {當前節點} node * @param {key} key */ [removeNode] (node, key) { if (node === null) /** the tree is empty or does not have this key who you want to remove. */ return null; if (key < node.key) /** the key of currrent node is bigger than target key. */ { node.left = this[removeNode](node.left, key); return node; } else if (key > node.key) /** smaller */ { node.right = this[removeNode](node.right, key); return node; } else /** 相等 */ { if (node.left === null && node.right === null) { /** * 當前節點沒有左右節點,能夠放心刪除 */ node = null; return node; } /** * 當前節點有一個節點,讓子節點`頂`上來 */ if (node.left === null) { node = node.right; return node } else if (node.right === null) { node = node.left; return node; } /** * 來到這裏表明當前節點有兩個子節點 */ let aux = this[findMinNode](node.right); // 找到右節點的最小節點 node.key = aux.key; // 把要刪除的節點的 key 覆蓋爲右側最小節點 key node.right = this[removeNode](node.right, aux.key); // 重構 right side tree (刪除上面找到的 aux 節點) return node; } } /** * 返回目標節點分支下最小節點 * @param {目標節點} node */ [findMinNode] (node) { while (node && node.left !== null) { node = node.left; } return node; }
好了,如今來一塊兒運行一下,看一下效果吧
遍歷 BST 通常有三種方式:
- 先序 - 中序 - 後序
seaconch 畫了 3 張圖幫助理解:
先序遍歷:
中序遍歷:
後序遍歷:
這裏咱們只演示中序遍歷的代碼
所謂前序,中序,後序通常都是指具體操做的位置,在這裏表示回調函數的位置
定義私有函數
const inOrderTraverseNode = Symbol('inOrderTraverseNode');
/** * 中序遍歷,標準名稱爲: `inOrderTraverse` */ middleOrderTraverse(cb) { this[inOrderTraverseNode](this.root, cb); } /** * * @param {當前節點} node * @param {回調} cb */ [inOrderTraverseNode] (node, cb) { if (node !== null) { this[inOrderTraverseNode](node.left, cb); cb(node.key); // 回調在中間就是中序 this[inOrderTraverseNode](node.right, cb); } }
結果是按照順序輸出了各個節點的 key:
Adelson-Velskii-Landi(AVL) 自平衡樹
BST 有必定的問題,好比當你添加了不少 大於 root 的元素,而只添加了不多的小於 root 的元素,那麼 BST 將嚴重失衡,最直觀的一個說明就是,獲取最大值的速度明顯沒有獲取最小值的速度快。
AVL 樹就是爲了解決 BST 失衡的問題
AVL 在每次 添加 或 刪除 元素的時候,嘗試自平衡,使左右子樹高度差 >= 1
(hr(右子樹高度) - hl(左子樹高度) in (-1, 0, 1))
源碼以下:
/** * 節點 */ class Node { constructor (key) { this.key = key; this.left = null; this.right = null; } } const insertNode = Symbol('insertNode'); const removeNode = Symbol('removeNode'); const findMinNode = Symbol('findMinNode'); const minNode = Symbol('minNode'); const maxNode = Symbol('maxNode'); const searchNode = Symbol('searchNode'); const inOrderTraverseNode = Symbol('inOrderTraverseNode'); const preOrderTraverseNode = Symbol('preOrderTraverseNode'); const postOrderTraverseNode = Symbol('postOrderTraverseNode'); /** * 二叉排序樹 */ class BinarySearchTree { constructor() { this.root = null; } /** * 插入節點 */ insert(key) { let newNode = new Node(key); if (this.root === null) this.root = newNode; else this[insertNode](this.root, newNode); } /** * 插入節點 * @param {當前節點} node * @param {新節點} newNode */ [insertNode] (node, newNode) { if (newNode.key < node.key) { if (node.left === null) node.left = newNode; else this[insertNode](node.left, newNode); } else { if (node.right === null) node.right = newNode; else this[insertNode](node.right, newNode); } } /** * 刪除節點 */ remove(key) { this[removeNode](this.root, key); } /** * 刪除節點,返回刪除後的 tree * @param {當前節點} node * @param {key} key */ [removeNode] (node, key) { if (node === null) /** the tree is empty or does not have this key who you want to remove. */ return null; if (key < node.key) /** the key of currrent node is bigger than target key. */ { node.left = this[removeNode](node.left, key); return node; } else if (key > node.key) /** smaller */ { node.right = this[removeNode](node.right, key); return node; } else /** 相等 */ { if (node.left === null && node.right === null) { /** * 當前節點沒有左右節點,能夠放心刪除 */ node = null; return node; } /** * 當前節點有一個節點,讓子節點`頂`上來 */ if (node.left === null) { node = node.right; return node } else if (node.right === null) { node = node.left; return node; } /** * 來到這裏表明當前節點有兩個子節點 */ let aux = this[findMinNode](node.right); // 找到右節點的最小節點 node.key = aux.key; // 把要刪除的節點的 key 覆蓋爲右側最小節點 key node.right = this[removeNode](node.right, aux.key); // 重構 right side tree (刪除上面找到的 aux 節點) return node; } } /** * 返回目標節點分支下最小節點 * @param {目標節點} node */ [findMinNode] (node) { while (node && node.left !== null) { node = node.left; } return node; } /** * 獲取最小節點 key */ min() { return this[minNode](this.root); } /** * 獲取最小節點 key * @param {節點} node */ [minNode] (node) { if (node) { while (node && node.left !== null) { node = node.left; } return node.key; } return null; } /** * 獲取最大節點 key */ max() { return this[maxNode](this.root); } /** * 獲取最大節點 key * @param {節點} node */ [maxNode] (node) { if (node) { while (node && node.right !== null) { node = node.right; } return node.key; } return null; } /** * 是否存在目標 key */ existKey(key) { return this[searchNode](this.root, key); } /** * * @param {當前節點} node * @param {key} key */ [searchNode] (node, key) { if (node === null) return false; if (key < node.key) return this[searchNode](node.left, key); else if (key > node.key) return this[searchNode](node.right, key); else return true; } /** * 中序遍歷,標準名稱爲: `inOrderTraverse` */ middleOrderTraverse(cb) { this[inOrderTraverseNode](this.root, cb); } /** * * @param {當前節點} node * @param {回調} cb */ [inOrderTraverseNode] (node, cb) { if (node !== null) { this[inOrderTraverseNode](node.left, cb); cb(node.key); // 回調在中間就是中序 this[inOrderTraverseNode](node.right, cb); } } preOrderTraverse(cb) { this[preOrderTraverseNode](this.root, cb); } /** * * @param {*} node * @param {*} cb */ [preOrderTraverseNode] (node, cb) { if (node !== null) { cb(node.key); // 回調在前 this[preOrderTraverseNode](node.left, cb); this[preOrderTraverseNode](node.right, cb); } } postOrderTraverse(cb) { this[postOrderTraverseNode](this.root, cb); } /** * * @param {*} node * @param {*} cb */ [postOrderTraverseNode] (node, cb) { if (node !== null) { this[postOrderTraverseNode](node.left, cb); this[postOrderTraverseNode](node.right, cb); cb(node.key); // 回調在後 } } } /** * 初始化數據 * @param {樹} tree */ function initialization(tree) { let treeKeys = [50, 25, 13, 5, 19, 35, 28, 49, 75, 64, 56, 74, 85, 79, 99]; treeKeys.forEach( key => tree.insert(key) ) } let tree = new BinarySearchTree(); initialization(tree); // tree.preOrderTraverse(v => console.log(v)); tree.middleOrderTraverse(v => console.log(v)); // tree.postOrderTraverse(v => console.log(v)); // console.log('the min node.key is: ', tree.min()); // console.log('the max node.key is: ', tree.max()); // let tempKey = 49; // console.log('the key of [', tempKey, ']', tree.existKey(tempKey) ? 'real' : 'not', 'exist.'); // tree.remove(49) // console.log('remove key [', tempKey, ']'); // console.log('the key of [', tempKey, ']', tree.existKey(tempKey) ? 'real' : 'not', 'exist.');