【從蛋殼到滿天飛】JS 數據結構解析和算法實現,所有文章大概的內容以下: Arrays(數組)、Stacks(棧)、Queues(隊列)、LinkedList(鏈表)、Recursion(遞歸思想)、BinarySearchTree(二分搜索樹)、Set(集合)、Map(映射)、Heap(堆)、PriorityQueue(優先隊列)、SegmentTree(線段樹)、Trie(字典樹)、UnionFind(並查集)、AVLTree(AVL 平衡樹)、RedBlackTree(紅黑平衡樹)、HashTable(哈希表)html
源代碼有三個:ES6(單個單個的 class 類型的 js 文件) | JS + HTML(一個 js 配合一個 html)| JAVA (一個一個的工程)node
所有源代碼已上傳 github,點擊我吧,光看文章可以掌握兩成,動手敲代碼、動腦思考、畫圖才能夠掌握八成。git
本文章適合 對數據結構想了解而且感興趣的人羣,文章風格一如既往如此,就以爲手機上看起來比較方便,這樣顯得比較有條理,整理這些筆記加源碼,時間跨度也算將近半年時間了,但願對想學習數據結構的人或者正在學習數據結構的人羣有幫助。github
在以前實現的二分搜索樹的問題算法
[1, 2, 3, 4, 5, 6]
後,在現有的二分搜索樹中添加一些機制,從而可以維持平衡二叉樹的性質,編程
A
delson-V
elsky 和 E.M.L
andis,平衡二叉樹數組
在本身實現的堆中引入了徹底二叉樹的概念,數據結構
本身實現的線段樹也是一種平衡二叉樹框架
不管是堆仍是線段樹都是平衡二叉樹的例子dom
在 AVL 樹中定義平衡二叉樹
一個葉子節點
相應的高度差都不能超過一,一個節點
它的左右子樹高度差不能超過一,O(logn)
級別的。// 知足AVL定義的平衡二叉樹
// (12)
// / \
// (8) (18)
// /\ /
// (5)(11)(17)
// /
// (4)
複製代碼
對上面圖中的 AVL 平衡二叉樹進行添加節點
// 添加節點2和節點7後的樹
// (12)
// / \
// (8) (18)
// / \ /
// (5) (11) (17)
// / \
// (4) (7)
// /
// (2)
//
複製代碼
讓上面圖中的樹維持一個平衡
O(logn)
這個級別的,對於以前所實現的二分搜索樹來講,
實現這個 AVL 平衡二叉樹相應的每個節點都記錄一下這個節點所在的高度,
其實這個記錄很是的簡單,對於葉子節點來講它所對應的高度就是 1,
也就是節點 2 對應的高度爲 1;節點 4 下面只有一個葉子節點,那麼對應的高度就是 2;
節點 7 也是一個葉子節點,因此它的高度值也是 1;
可是對於節點 5 來講它有左右兩棵子樹,左邊的子樹高度爲 2,右邊的子樹高度爲 1,
相應的節點 5 這個節點它的高度就是左右兩棵子樹中最高的那棵樹再加上自身的 1,
那麼這樣一來節點 5 它的高度就是 2+1=3;很是好理解,節點 11 它的高度就是 1,
其它的如此類推,以下圖的標註所示。
// 節點名稱加高度 小括號中的是節點名稱,中括號中的是高度值
// 【5】(12)
// / \
// 【4】(8) (18)【2】
// / \ /
// 【3】(5) 【1】(11) (17)【1】
// / \
// 【2】(4) (7)【1】
// /
// 【1】(2)
//
複製代碼
平衡因子
對整棵樹每個節點都標註上高度值以後,
相應的計算一個稱之爲平衡因子的這樣的一個數,
這個名詞雖然聽起來比較複雜,實際上很是的簡單,
它就是指對於每個節點而言,它的左右子樹的高度差,
計算高度值的差可使用左子樹的高度減去右子樹的高度差。
對於節點 2 來講它是要給葉子節點,
它的左右兩棵子樹至關因而兩棵空樹,空樹的高度值能夠記爲 0,
相應的葉子節點的平衡因子其實就是0 - 0 = 0
,最後的結果也爲 0;
對於節點 4 來講它的左子樹對應的高度值爲 1,右子樹爲空所對應的高度值爲 0,
那麼它的平衡因子就是1 - 0 = 1
,最後的結果爲 1;
節點 7 是個葉子節點,那麼它的平衡因子就是 0;
節點 5 兩棵子樹的高度差是2 - 1 = 1
,最終的結果爲 1;
節點 11 是葉子節點,因此他的平衡因子爲 0;
節點 8 的兩棵子樹的高度查實3 - 1 = 2
,最終的結果爲 2。
這就意味着節點 8 這個左右子樹的高度差已經超過了一,
經過這個平衡因子就能夠看出來這棵樹已經不是一棵平衡二叉樹了,
以下圖這樣標註出平衡因子以後,
一旦在一棵樹中有一個節點它的平衡因子是大於等於 2 或者小於等於-2 的,
換句話說它的絕對值是等於 2,那麼整棵樹就再也不是一棵平衡二叉樹了。
以下圖所示,節點 12 和節點 8 的平衡因子的絕對值都是 2,
那麼就說明這兩個節點破壞了平衡二叉樹對應的那個性質,
這個對應的性質就是 左右子樹的高度差超過了一,
一旦能夠計算出每個節點的平衡因子,
相應的也能夠看出來當前的這棵樹是不是一棵平衡二叉樹,
不只如此在後續的 AVL 實現中也會藉助每個節點
它對應的平衡因子來決定是否要進行一些特殊的操做,
從而來維持整棵樹是不是一棵平衡二叉樹。
// 1. 節點名稱 加高度值 加平衡因子值
// 1. 小括號中的是節點名稱,
// 2. 中括號中的是高度值,
// 3. 大括號中的是平衡因子
//
// 2. 平衡因子值 = 左右子樹的高度差
// 3. 左右子樹的高度差 = 左子樹的高度值 - 右子樹的高度值
// {2}【5】(12)
// / \
// {2}【4】(8) (18)【2】{1}
// / \ /
// {1}【3】(5) {0}【1】(11)(17)【1】{0}
// / \
// {1}【2】(4) (7)【1】{1}
// /
// {0}【1】(2)
//
複製代碼
經過對每個節點標註相應的高度
而後經過這個高度很是簡單的計算出平衡因子,
最後在經過平衡因子來決定是否進行一些特殊的操做,
從而維持整棵樹是一棵平衡二叉樹的性質。
AVLTree
// 自定義AVL樹節點 AVLTreeNode
class MyAVLTreeNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.height = 1;
}
// @Override toString 2018-11-24-jwl
toString() {
return this.key + '--->' + this.value + '--->' + this.height;
}
}
// 自定義AVL樹 AVLTree
class MyAVLTree {
constructor() {
this.root = null;
this.size = 0;
}
// 比較的功能
compare(keyA, keyB) {
if (keyA === null || keyB === null)
throw new Error("key is error. key can't compare.");
if (keyA > keyB) return 1;
else if (keyA < keyB) return -1;
else return 0;
}
// 獲取某個節點的高度 -
getHeight(node) {
// 節點爲空 返回0
if (!node) return 0;
// 直接返回這個節點的高度
return node.height;
}
// 獲取一個節點的平衡因子 -
getBalanceFactor(node) {
// 節點爲空 返回0
if (!node) return 0;
// 左右子樹的高度值
const leftHeight = this.getHeight(node.left);
const rightHeight = this.getHeight(node.right);
// 左子樹的高度 - 右子樹高度的值 = 平衡因子
return leftHeight - rightHeight;
}
// 根據key獲取節點 -
getNode(node, key) {
// 先解決最基本的問題
if (node === null) return null;
// 開始將複雜的問題 逐漸縮小規模
// 從而求出小問題的解,最後構建出原問題的解
switch (this.compare(node.key, key)) {
case 1: // 向左找
return this.getNode(node.left, key);
break;
case -1: // 向右找
return this.getNode(node.right, key);
break;
case 0: // 找到了
return node;
break;
default:
throw new Error(
'compare result is error. compare result : 0、 一、 -1 .'
);
break;
}
}
// 添加操做 +
add(key, value) {
this.root = this.recursiveAdd(this.root, key, value);
}
// 添加操做 遞歸算法 -
recursiveAdd(node, key, value) {
// 解決最簡單的問題
if (node === null) {
this.size++;
return new MyAVLTreeNode(key, value);
}
// 將複雜的問題規模逐漸變小,
// 從而求出小問題的解,從而構建出原問題的答案
if (this.compare(node.key, key) > 0)
node.left = this.recursiveAdd(node.left, key, value);
else if (this.compare(node.key, key) < 0)
node.right = this.recursiveAdd(node.right, key, value);
else node.value = value;
// 在這裏對節點的高度進行從新計算 節點自己高度爲1
// 計算方式: 1 + 左右子樹的height值最大的那個height值
node.height =
1 + Math.max(this.getHeight(node.left), this.getHeight(node.right));
// 計算一個節點的平衡因子
const balanceFactor = this.getBalanceFactor(node);
// 若是平衡因子的絕對值大於1 說明不知足AVL平衡二叉樹的性質了
if (Math.abs(balanceFactor) > 1) {
console.log(
node.toString() + ' unbalanced : ' + balanceFactor + '\r\n'
);
document.body.innerHTML +=
node.toString() + ' unbalanced : ' + balanceFactor + '<br/>';
}
return node;
}
// 刪除操做 返回被刪除的元素 +
remove(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
this.root = this.recursiveRemove(this.root, key);
return node.value;
}
// 刪除操做 遞歸算法 +
recursiveRemove(node, key) {
// 解決最基本的問題
if (node === null) return null;
if (this.compare(node.key, key) > 0) {
node.left = this.recursiveRemove(node.left, key);
return node;
} else if (this.compare(node.key, key) < 0) {
node.right = this.recursiveRemove(node.right, key);
return node;
} else {
// 當前節點的key 與 待刪除的key的那個節點相同
// 有三種狀況
// 1. 當前節點沒有左子樹,那麼只有讓當前節點的右子樹直接覆蓋當前節點,就表示當前節點被刪除了
// 2. 當前節點沒有右子樹,那麼只有讓當前節點的左子樹直接覆蓋當前節點,就表示當前節點被刪除了
// 3. 當前節點左右子樹都有, 那麼又分兩種狀況,使用前驅刪除法或者後繼刪除法
// 1. 前驅刪除法:使用當前節點的左子樹上最大的那個節點覆蓋當前節點
// 2. 後繼刪除法:使用當前節點的右子樹上最小的那個節點覆蓋當前節點
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
} else if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
} else {
let predecessor = this.maximum(node.left);
node.left = this.removeMax(node.left);
this.size++;
// 開始嫁接 當前節點的左右子樹
predecessor.left = node.left;
predecessor.right = node.right;
// 將當前節點從根節點剔除
node = node.left = node.right = null;
this.size--;
// 返回嫁接後的新節點
return predecessor;
}
}
}
// 刪除操做的兩個輔助函數
// 獲取最大值、刪除最大值
// 之前驅的方式 來輔助刪除操做的函數
// 獲取最大值
maximum(node) {
// 不再能往右了,說明當前節點已是最大的了
if (node.right === null) return node;
// 將複雜的問題漸漸減少規模,從而求出小問題的解,最後用小問題的解構建出原問題的答案
return this.maximum(node.right);
}
// 刪除最大值
removeMax(node) {
// 解決最基本的問題
if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
// 開始化歸
node.right = this.removeMax(node.right);
return node;
}
// 查詢操做 返回查詢到的元素 +
get(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
return node.value;
}
// 修改操做 +
set(key, value) {
let node = this.getNode(this.root, key);
if (node === null) throw new Error(key + " doesn't exist.");
node.value = value;
}
// 返回是否包含該key的元素的判斷值 +
contains(key) {
return this.getNode(this.root, key) !== null;
}
// 返回映射中實際的元素個數 +
getSize() {
return this.size;
}
// 返回映射中是否爲空的判斷值 +
isEmpty() {
return this.size === 0;
}
// @Override toString() 2018-11-05-jwl
toString() {
let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
// 以非遞歸的前序遍歷 輸出字符串
let stack = new MyLinkedListStack();
stack.push(this.root);
if (this.root === null) stack.pop();
while (!stack.isEmpty()) {
let node = stack.pop();
if (node.left !== null) stack.push(node.left);
if (node.right !== null) stack.push(node.right);
if (node.left === null && node.right === null) {
mapInfo += ` ${node.toString()} \r\n`;
document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
} else {
mapInfo += ` ${node.toString()}, \r\n`;
document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
}
}
mapInfo += ` ] \r\n`;
document.body.innerHTML += ` ] <br/><br/>`;
return mapInfo;
}
}
複製代碼
Main
// main 函數
class Main {
constructor() {
this.alterLine('AVLTree Area');
// 千級別
const openCount = 100; // 操做數
const rank = 10000000;
// 生成同一份測試數據的輔助代碼
const random = Math.random;
const array = new Array(openCount);
// 生成同一份測試數據
for (var i = 0; i < openCount; i++)
array[i] = Math.floor(random() * rank);
// 建立AVL樹實例
const avl = new MyAVLTree();
for (const value of array) avl.add(value);
}
// 將內容顯示在頁面上
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();
};
複製代碼
AVLTree
// 自定義AVL樹節點 AVLTreeNode
class MyAVLTreeNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.height = 1;
}
// @Override toString 2018-11-24-jwl
toString() {
return this.key + '--->' + this.value + '--->' + this.height;
}
}
// 自定義AVL樹 AVLTree
class MyAVLTree {
constructor() {
this.root = null;
this.size = 0;
}
// 比較的功能
compare(keyA, keyB) {
if (keyA === null || keyB === null)
throw new Error("key is error. key can't compare.");
if (keyA > keyB) return 1;
else if (keyA < keyB) return -1;
else return 0;
}
// 獲取某個節點的高度 -
getHeight(node) {
// 節點爲空 返回0
if (!node) return 0;
// 直接返回這個節點的高度
return node.height;
}
// 獲取一個節點的平衡因子 -
getBalanceFactor(node) {
// 節點爲空 返回0
if (!node) return 0;
// 左右子樹的高度值
const leftHeight = this.getHeight(node.left);
const rightHeight = this.getHeight(node.right);
// 左子樹的高度 - 右子樹高度的值 = 平衡因子
return leftHeight - rightHeight;
}
// 根據key獲取節點 -
getNode(node, key) {
// 先解決最基本的問題
if (node === null) return null;
// 開始將複雜的問題 逐漸縮小規模
// 從而求出小問題的解,最後構建出原問題的解
switch (this.compare(node.key, key)) {
case 1: // 向左找
return this.getNode(node.left, key);
break;
case -1: // 向右找
return this.getNode(node.right, key);
break;
case 0: // 找到了
return node;
break;
default:
throw new Error(
'compare result is error. compare result : 0、 一、 -1 .'
);
break;
}
}
// 添加操做 +
add(key, value) {
this.root = this.recursiveAdd(this.root, key, value);
}
// 添加操做 遞歸算法 -
recursiveAdd(node, key, value) {
// 解決最簡單的問題
if (node === null) {
this.size++;
return new MyAVLTreeNode(key, value);
}
// 將複雜的問題規模逐漸變小,
// 從而求出小問題的解,從而構建出原問題的答案
if (this.compare(node.key, key) > 0)
node.left = this.recursiveAdd(node.left, key, value);
else if (this.compare(node.key, key) < 0)
node.right = this.recursiveAdd(node.right, key, value);
else node.value = value;
// 在這裏對節點的高度進行從新計算 節點自己高度爲1
// 計算方式: 1 + 左右子樹的height值最大的那個height值
node.height =
1 + Math.max(this.getHeight(node.left), this.getHeight(node.right));
// // 計算一個節點的平衡因子
// const balanceFactor = this.getBalanceFactor(node);
// // 若是平衡因子的絕對值大於1 說明不知足AVL平衡二叉樹的性質了
// if (Math.abs(balanceFactor) > 1) {
// console.log(node.toString() + " unbalanced : " + balanceFactor + "\r\n");
// document.body.innerHTML += node.toString() + " unbalanced : " + balanceFactor + "<br/>";
// }
return node;
}
// 刪除操做 返回被刪除的元素 +
remove(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
this.root = this.recursiveRemove(this.root, key);
return node.value;
}
// 刪除操做 遞歸算法 +
recursiveRemove(node, key) {
// 解決最基本的問題
if (node === null) return null;
if (this.compare(node.key, key) > 0) {
node.left = this.recursiveRemove(node.left, key);
return node;
} else if (this.compare(node.key, key) < 0) {
node.right = this.recursiveRemove(node.right, key);
return node;
} else {
// 當前節點的key 與 待刪除的key的那個節點相同
// 有三種狀況
// 1. 當前節點沒有左子樹,那麼只有讓當前節點的右子樹直接覆蓋當前節點,就表示當前節點被刪除了
// 2. 當前節點沒有右子樹,那麼只有讓當前節點的左子樹直接覆蓋當前節點,就表示當前節點被刪除了
// 3. 當前節點左右子樹都有, 那麼又分兩種狀況,使用前驅刪除法或者後繼刪除法
// 1. 前驅刪除法:使用當前節點的左子樹上最大的那個節點覆蓋當前節點
// 2. 後繼刪除法:使用當前節點的右子樹上最小的那個節點覆蓋當前節點
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
} else if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
} else {
let predecessor = this.maximum(node.left);
node.left = this.removeMax(node.left);
this.size++;
// 開始嫁接 當前節點的左右子樹
predecessor.left = node.left;
predecessor.right = node.right;
// 將當前節點從根節點剔除
node = node.left = node.right = null;
this.size--;
// 返回嫁接後的新節點
return predecessor;
}
}
}
// 刪除操做的兩個輔助函數
// 獲取最大值、刪除最大值
// 之前驅的方式 來輔助刪除操做的函數
// 獲取最大值
maximum(node) {
// 不再能往右了,說明當前節點已是最大的了
if (node.right === null) return node;
// 將複雜的問題漸漸減少規模,從而求出小問題的解,最後用小問題的解構建出原問題的答案
return this.maximum(node.right);
}
// 刪除最大值
removeMax(node) {
// 解決最基本的問題
if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
// 開始化歸
node.right = this.removeMax(node.right);
return node;
}
// 查詢操做 返回查詢到的元素 +
get(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
return node.value;
}
// 修改操做 +
set(key, value) {
let node = this.getNode(this.root, key);
if (node === null) throw new Error(key + " doesn't exist.");
node.value = value;
}
// 返回是否包含該key的元素的判斷值 +
contains(key) {
return this.getNode(this.root, key) !== null;
}
// 返回映射中實際的元素個數 +
getSize() {
return this.size;
}
// 返回映射中是否爲空的判斷值 +
isEmpty() {
return this.size === 0;
}
// 判斷當前這棵樹是不是一棵二分搜索樹,有二分搜索樹順序性
isBanarySearchTree() {
// 若是節點爲空 那麼這就是一棵空的二分搜索樹
if (!this.root) return true;
// 存儲二分搜索樹中的key
const list = new Array();
// 中序遍歷後,添加到list中的值會是以從小到大升序的樣子排列
this.inOrder(this.root, list);
// 從前日後判斷 list中的值是不是從小到大升序的排列
// 驗證 當前樹是否符合二分搜索樹的性質
for (var i = 1; i < list.length; i++)
if (list[i - 1] > list[i]) return false;
return true;
}
// 中序遍歷 輔助函數 -
inOrder(node, list) {
// 遞歸到底的狀況
if (!node) return;
// 中序遍歷時,添加到數組中的值會是以從小到大升序的樣子排列
this.inOrder(node.left, list);
list.push(node.key);
this.inOrder(node.right, list);
}
// 判斷該二叉樹是否一棵平衡二叉樹
isBalanced() {
return this.recursiveIsBalanced(this.root);
}
// 遞歸判斷某一個節點是否符合平衡二叉樹的定義 輔助函數 -
recursiveIsBalanced(node) {
// 可以遞歸到底,說明符合要求
// 空的節點左右孩子高度差確定爲0,
// 由於空樹沒有左右子樹,更加談不上下面去判斷它的左右子樹高度差是否會超過一。
if (!node) return true;
// 若是當前節點的高度差大於1 說明不符合要求
if (Math.abs(this.getBalanceFactor(node)) > 1) return false;
// 遞歸的去判斷當前節點的 左右子樹是否符合要求
return (
this.recursiveIsBalanced(node.left) &&
this.recursiveIsBalanced(node.right)
);
}
// @Override toString() 2018-11-05-jwl
toString() {
let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
// 以非遞歸的前序遍歷 輸出字符串
let stack = new MyLinkedListStack();
stack.push(this.root);
if (this.root === null) stack.pop();
while (!stack.isEmpty()) {
let node = stack.pop();
if (node.left !== null) stack.push(node.left);
if (node.right !== null) stack.push(node.right);
if (node.left === null && node.right === null) {
mapInfo += ` ${node.toString()} \r\n`;
document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
} else {
mapInfo += ` ${node.toString()}, \r\n`;
document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
}
}
mapInfo += ` ] \r\n`;
document.body.innerHTML += ` ] <br/><br/>`;
return mapInfo;
}
}
複製代碼
Main
// main 函數
class Main {
constructor() {
this.alterLine('AVLTree Area');
// 千級別
const openCount = 100; // 操做數
const rank = 10000000;
// 生成同一份測試數據的輔助代碼
const random = Math.random;
const array = new Array(openCount);
// 生成同一份測試數據
for (var i = 0; i < openCount; i++)
array[i] = Math.floor(random() * rank);
// 建立AVL樹實例
const avl = new MyAVLTree();
for (const value of array) avl.add(value);
// 輸出當前這棵avl樹是不是一個二分搜索樹
this.show('Is Binary Search Tree : ' + avl.isBanarySearchTree());
console.log('Is Binary Search Tree : ' + avl.isBanarySearchTree());
// 輸出當前這棵avl樹是不是一個平衡二叉樹
this.show('Is Balanced : ' + avl.isBalanced());
console.log('Is Balanced : ' + avl.isBalanced());
}
// 將內容顯示在頁面上
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();
};
複製代碼
AVL 樹是經過兩個主要的機制來實現自平衡的
AVL 樹維護自平衡在何時發生
大於1
或者小於-1
,AVL 樹維護平衡性的原理
右旋轉操做
// 最開始這棵樹是這種狀況 T1 < Z < T2 < X < T3 < Y < T4
// (Y)
// / \
// (X) (T4)
// / \
// (Z) (T3)
// / \
// (T1) (T2)
// 右旋轉後是這樣子,依然是T1 < Z < T2 < X < T3 < Y < T4
// (X)
// / \
// (Z) (Y)
// / \ / \
// (T1) (T2)(T3)(T4)
複製代碼
// 自定義AVL樹節點 AVLTreeNode
class MyAVLTreeNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.height = 1;
}
// @Override toString 2018-11-24-jwl
toString() {
return this.key + '--->' + this.value + '--->' + this.height;
}
}
// 自定義AVL樹 AVLTree
class MyAVLTree {
constructor() {
this.root = null;
this.size = 0;
}
// 比較的功能
compare(keyA, keyB) {
if (keyA === null || keyB === null)
throw new Error("key is error. key can't compare.");
if (keyA > keyB) return 1;
else if (keyA < keyB) return -1;
else return 0;
}
// 獲取某個節點的高度 -
getHeight(node) {
// 節點爲空 返回0
if (!node) return 0;
// 直接返回這個節點的高度
return node.height;
}
// 獲取一個節點的平衡因子 -
getBalanceFactor(node) {
// 節點爲空 返回0
if (!node) return 0;
// 左右子樹的高度值
const leftHeight = this.getHeight(node.left);
const rightHeight = this.getHeight(node.right);
// 左子樹的高度 - 右子樹高度的值 = 平衡因子
return leftHeight - rightHeight;
}
// 根據key獲取節點 -
getNode(node, key) {
// 先解決最基本的問題
if (node === null) return null;
// 開始將複雜的問題 逐漸縮小規模
// 從而求出小問題的解,最後構建出原問題的解
switch (this.compare(node.key, key)) {
case 1: // 向左找
return this.getNode(node.left, key);
break;
case -1: // 向右找
return this.getNode(node.right, key);
break;
case 0: // 找到了
return node;
break;
default:
throw new Error(
'compare result is error. compare result : 0、 一、 -1 .'
);
break;
}
}
// 添加操做 +
add(key, value) {
this.root = this.recursiveAdd(this.root, key, value);
}
// 添加操做 遞歸算法 -
recursiveAdd(node, key, value) {
// 解決最簡單的問題
if (node === null) {
this.size++;
return new MyAVLTreeNode(key, value);
}
// 將複雜的問題規模逐漸變小,
// 從而求出小問題的解,從而構建出原問題的答案
if (this.compare(node.key, key) > 0)
node.left = this.recursiveAdd(node.left, key, value);
else if (this.compare(node.key, key) < 0)
node.right = this.recursiveAdd(node.right, key, value);
else node.value = value;
// 在這裏對節點的高度進行從新計算 節點自己高度爲1
// 計算方式: 1 + 左右子樹的height值最大的那個height值
node.height =
1 + Math.max(this.getHeight(node.left), this.getHeight(node.right));
// 計算一個節點的平衡因子
const balanceFactor = this.getBalanceFactor(node);
// 平衡維護 右旋轉操做 平衡因子爲正數則表示左傾 反之爲右傾
if (balanceFactor > 1 && this.getBalanceFactor(node.left) >= 0) {
}
// // 若是平衡因子的絕對值大於1 說明不知足AVL平衡二叉樹的性質了
// if (Math.abs(balanceFactor) > 1) {
// console.log(node.toString() + " unbalanced : " + balanceFactor + "\r\n");
// document.body.innerHTML += node.toString() + " unbalanced : " + balanceFactor + "<br/>";
// }
return node;
}
// 刪除操做 返回被刪除的元素 +
remove(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
this.root = this.recursiveRemove(this.root, key);
return node.value;
}
// 刪除操做 遞歸算法 +
recursiveRemove(node, key) {
// 解決最基本的問題
if (node === null) return null;
if (this.compare(node.key, key) > 0) {
node.left = this.recursiveRemove(node.left, key);
return node;
} else if (this.compare(node.key, key) < 0) {
node.right = this.recursiveRemove(node.right, key);
return node;
} else {
// 當前節點的key 與 待刪除的key的那個節點相同
// 有三種狀況
// 1. 當前節點沒有左子樹,那麼只有讓當前節點的右子樹直接覆蓋當前節點,就表示當前節點被刪除了
// 2. 當前節點沒有右子樹,那麼只有讓當前節點的左子樹直接覆蓋當前節點,就表示當前節點被刪除了
// 3. 當前節點左右子樹都有, 那麼又分兩種狀況,使用前驅刪除法或者後繼刪除法
// 1. 前驅刪除法:使用當前節點的左子樹上最大的那個節點覆蓋當前節點
// 2. 後繼刪除法:使用當前節點的右子樹上最小的那個節點覆蓋當前節點
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
} else if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
} else {
let predecessor = this.maximum(node.left);
node.left = this.removeMax(node.left);
this.size++;
// 開始嫁接 當前節點的左右子樹
predecessor.left = node.left;
predecessor.right = node.right;
// 將當前節點從根節點剔除
node = node.left = node.right = null;
this.size--;
// 返回嫁接後的新節點
return predecessor;
}
}
}
// 刪除操做的兩個輔助函數
// 獲取最大值、刪除最大值
// 之前驅的方式 來輔助刪除操做的函數
// 獲取最大值
maximum(node) {
// 不再能往右了,說明當前節點已是最大的了
if (node.right === null) return node;
// 將複雜的問題漸漸減少規模,從而求出小問題的解,最後用小問題的解構建出原問題的答案
return this.maximum(node.right);
}
// 刪除最大值
removeMax(node) {
// 解決最基本的問題
if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
// 開始化歸
node.right = this.removeMax(node.right);
return node;
}
// 查詢操做 返回查詢到的元素 +
get(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
return node.value;
}
// 修改操做 +
set(key, value) {
let node = this.getNode(this.root, key);
if (node === null) throw new Error(key + " doesn't exist.");
node.value = value;
}
// 返回是否包含該key的元素的判斷值 +
contains(key) {
return this.getNode(this.root, key) !== null;
}
// 返回映射中實際的元素個數 +
getSize() {
return this.size;
}
// 返回映射中是否爲空的判斷值 +
isEmpty() {
return this.size === 0;
}
// 判斷當前這棵樹是不是一棵二分搜索樹,有二分搜索樹順序性
isBanarySearchTree() {
// 若是節點爲空 那麼這就是一棵空的二分搜索樹
if (!this.root) return true;
// 存儲二分搜索樹中的key
const list = new Array();
// 中序遍歷後,添加到list中的值會是以從小到大升序的樣子排列
this.inOrder(this.root, list);
// 從前日後判斷 list中的值是不是從小到大升序的排列
// 驗證 當前樹是否符合二分搜索樹的性質
for (var i = 1; i < list.length; i++)
if (list[i - 1] > list[i]) return false;
return true;
}
// 中序遍歷 輔助函數 -
inOrder(node, list) {
// 遞歸到底的狀況
if (!node) return;
// 中序遍歷時,添加到數組中的值會是以從小到大升序的樣子排列
this.inOrder(node.left, list);
list.push(node.key);
this.inOrder(node.right, list);
}
// 判斷該二叉樹是否一棵平衡二叉樹
isBalanced() {
return this.recursiveIsBalanced(this.root);
}
// 遞歸判斷某一個節點是否符合平衡二叉樹的定義 輔助函數 -
recursiveIsBalanced(node) {
// 可以遞歸到底,說明符合要求
// 空的節點左右孩子高度差確定爲0,
// 由於空樹沒有左右子樹,更加談不上下面去判斷它的左右子樹高度差是否會超過一。
if (!node) return true;
// 若是當前節點的高度差大於1 說明不符合要求
if (Math.abs(this.getBalanceFactor(node)) > 1) return false;
// 遞歸的去判斷當前節點的 左右子樹是否符合要求
return (
this.recursiveIsBalanced(node.left) &&
this.recursiveIsBalanced(node.right)
);
}
// @Override toString() 2018-11-05-jwl
toString() {
let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
// 以非遞歸的前序遍歷 輸出字符串
let stack = new MyLinkedListStack();
stack.push(this.root);
if (this.root === null) stack.pop();
while (!stack.isEmpty()) {
let node = stack.pop();
if (node.left !== null) stack.push(node.left);
if (node.right !== null) stack.push(node.right);
if (node.left === null && node.right === null) {
mapInfo += ` ${node.toString()} \r\n`;
document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
} else {
mapInfo += ` ${node.toString()}, \r\n`;
document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
}
}
mapInfo += ` ] \r\n`;
document.body.innerHTML += ` ] <br/><br/>`;
return mapInfo;
}
}
複製代碼
// main 函數
class Main {
constructor() {
this.alterLine('AVLTree Area');
// 千級別
const openCount = 100; // 操做數
const rank = 10000000;
// 生成同一份測試數據的輔助代碼
const random = Math.random;
const array = new Array(openCount);
// 生成同一份測試數據
for (var i = 0; i < openCount; i++)
array[i] = Math.floor(random() * rank);
// 建立AVL樹實例
const avl = new MyAVLTree();
for (const value of array) avl.add(value);
// 輸出當前這棵avl樹是不是一個二分搜索樹
this.show('Is Binary Search Tree : ' + avl.isBanarySearchTree());
console.log('Is Binary Search Tree : ' + avl.isBanarySearchTree());
// 輸出當前這棵avl樹是不是一個平衡二叉樹
this.show('Is Balanced : ' + avl.isBalanced());
console.log('Is Balanced : ' + avl.isBalanced());
}
// 將內容顯示在頁面上
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();
};
複製代碼
右旋轉
// 對節點y進行向右旋轉操做,返回旋轉後新的根節點x
// y x
// / \ / \
// x T4 向右旋轉 (y) z y
// / \ - - - - - - - -> / \ / \
// z T3 T1 T2 T3 T4
// / \
// T1 T2
複製代碼
左旋轉
// 最開始這棵樹是這種狀況 T4 < Y < T3 < X < T1 < Z < T2
// (Y)
// / \
// (T4) (X)
// / \
// (T3) (Z)
// / \
// (T1) (T2)
// 左旋轉後是這樣子,依然是T4 < Y < T3 < X < T1 < Z < T2
// (X)
// / \
// (Y) (Z)
// / \ / \
// (T4)(T3)(T1)(T2)
複製代碼
不管是左旋轉仍是右旋轉都是一種狀況
使用了左旋轉和右旋轉來進行平衡性的維護以後
AVLTree
// 自定義AVL樹節點 AVLTreeNode
class MyAVLTreeNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.height = 1;
}
// @Override toString 2018-11-24-jwl
toString() {
return this.key + '--->' + this.value + '--->' + this.height;
}
}
// 自定義AVL樹 AVLTree
class MyAVLTree {
constructor() {
this.root = null;
this.size = 0;
}
// 比較的功能
compare(keyA, keyB) {
if (keyA === null || keyB === null)
throw new Error("key is error. key can't compare.");
if (keyA > keyB) return 1;
else if (keyA < keyB) return -1;
else return 0;
}
// 獲取某個節點的高度 -
getHeight(node) {
// 節點爲空 返回0
if (!node) return 0;
// 直接返回這個節點的高度
return node.height;
}
// 獲取一個節點的平衡因子 -
getBalanceFactor(node) {
// 節點爲空 返回0
if (!node) return 0;
// 左右子樹的高度值
const leftHeight = this.getHeight(node.left);
const rightHeight = this.getHeight(node.right);
// 左子樹的高度 - 右子樹高度的值 = 平衡因子
return leftHeight - rightHeight;
}
// 根據key獲取節點 -
getNode(node, key) {
// 先解決最基本的問題
if (node === null) return null;
// 開始將複雜的問題 逐漸縮小規模
// 從而求出小問題的解,最後構建出原問題的解
switch (this.compare(node.key, key)) {
case 1: // 向左找
return this.getNode(node.left, key);
break;
case -1: // 向右找
return this.getNode(node.right, key);
break;
case 0: // 找到了
return node;
break;
default:
throw new Error(
'compare result is error. compare result : 0、 一、 -1 .'
);
break;
}
}
// 對節點y進行向右旋轉操做,返回旋轉後新的根節點x
// y x
// / \ / \
// x T4 向右旋轉 (y) z y
// / \ - - - - - - - -> / \ / \
// z T3 T1 T2 T3 T4
// / \
// T1 T2
rightRotate(y) {
const x = y.left;
const T3 = x.right;
// 向右旋轉的過程
y.left = T3;
x.right = y;
// 更新節點的height值 只須要更新x和y的便可
y.height =
1 + Math.max(this.getHeight(y.left), this.getHeight(y.right));
x.height =
1 + Math.max(this.getHeight(x.left), this.getHeight(x.right));
// 返回 新節點 x
return x;
}
// 對節點y進行向左旋轉操做,返回旋轉後新的根節點x
// y x
// / \ / \
// T1 x 向左旋轉 (y) y z
// / \ - - - - - - - -> / \ / \
// T2 z T1 T2 T3 T4
// / \
// T3 T4
leftRotate(y) {
const x = y.right;
const T2 = x.left;
// 向左旋轉的過程
y.right = T2;
x.left = y;
// 更新節點的height值 只須要更新x和y的便可
y.height =
1 + Math.max(this.getHeight(y.left), this.getHeight(y.right));
x.height =
1 + Math.max(this.getHeight(x.left), this.getHeight(x.right));
// 返回 新節點 x
return x;
}
// 添加操做 +
add(key, value) {
this.root = this.recursiveAdd(this.root, key, value);
}
// 添加操做 遞歸算法 -
recursiveAdd(node, key, value) {
// 解決最簡單的問題
if (node === null) {
this.size++;
return new MyAVLTreeNode(key, value);
}
// 將複雜的問題規模逐漸變小,
// 從而求出小問題的解,從而構建出原問題的答案
if (this.compare(node.key, key) > 0)
node.left = this.recursiveAdd(node.left, key, value);
else if (this.compare(node.key, key) < 0)
node.right = this.recursiveAdd(node.right, key, value);
else node.value = value;
// 在這裏對節點的高度進行從新計算 節點自己高度爲1
// 計算方式: 1 + 左右子樹的height值最大的那個height值
node.height =
1 + Math.max(this.getHeight(node.left), this.getHeight(node.right));
// 計算一個節點的平衡因子
const balanceFactor = this.getBalanceFactor(node);
// 若是平衡因子的絕對值大於1 說明不知足AVL平衡二叉樹的性質了
if (Math.abs(balanceFactor) > 1) {
console.log(
node.toString() + ' unbalanced : ' + balanceFactor + '\r\n'
);
document.body.innerHTML +=
node.toString() + ' unbalanced : ' + balanceFactor + '<br/>';
}
// 平衡維護 右旋轉操做 平衡因子爲正數則表示左傾 反之爲右傾
if (balanceFactor > 1 && this.getBalanceFactor(node.left) >= 0)
return this.rightRotate(node);
// 平衡維護 右旋轉操做 平衡因子爲負數則表示右傾 反之爲左傾
if (balanceFactor < -1 && this.getBalanceFactor(node.left) <= 0)
return this.leftRotate(node);
return node;
}
// 刪除操做 返回被刪除的元素 +
remove(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
this.root = this.recursiveRemove(this.root, key);
return node.value;
}
// 刪除操做 遞歸算法 +
recursiveRemove(node, key) {
// 解決最基本的問題
if (node === null) return null;
if (this.compare(node.key, key) > 0) {
node.left = this.recursiveRemove(node.left, key);
return node;
} else if (this.compare(node.key, key) < 0) {
node.right = this.recursiveRemove(node.right, key);
return node;
} else {
// 當前節點的key 與 待刪除的key的那個節點相同
// 有三種狀況
// 1. 當前節點沒有左子樹,那麼只有讓當前節點的右子樹直接覆蓋當前節點,就表示當前節點被刪除了
// 2. 當前節點沒有右子樹,那麼只有讓當前節點的左子樹直接覆蓋當前節點,就表示當前節點被刪除了
// 3. 當前節點左右子樹都有, 那麼又分兩種狀況,使用前驅刪除法或者後繼刪除法
// 1. 前驅刪除法:使用當前節點的左子樹上最大的那個節點覆蓋當前節點
// 2. 後繼刪除法:使用當前節點的右子樹上最小的那個節點覆蓋當前節點
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
} else if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
} else {
let predecessor = this.maximum(node.left);
node.left = this.removeMax(node.left);
this.size++;
// 開始嫁接 當前節點的左右子樹
predecessor.left = node.left;
predecessor.right = node.right;
// 將當前節點從根節點剔除
node = node.left = node.right = null;
this.size--;
// 返回嫁接後的新節點
return predecessor;
}
}
}
// 刪除操做的兩個輔助函數
// 獲取最大值、刪除最大值
// 之前驅的方式 來輔助刪除操做的函數
// 獲取最大值
maximum(node) {
// 不再能往右了,說明當前節點已是最大的了
if (node.right === null) return node;
// 將複雜的問題漸漸減少規模,從而求出小問題的解,最後用小問題的解構建出原問題的答案
return this.maximum(node.right);
}
// 刪除最大值
removeMax(node) {
// 解決最基本的問題
if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
// 開始化歸
node.right = this.removeMax(node.right);
return node;
}
// 查詢操做 返回查詢到的元素 +
get(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
return node.value;
}
// 修改操做 +
set(key, value) {
let node = this.getNode(this.root, key);
if (node === null) throw new Error(key + " doesn't exist.");
node.value = value;
}
// 返回是否包含該key的元素的判斷值 +
contains(key) {
return this.getNode(this.root, key) !== null;
}
// 返回映射中實際的元素個數 +
getSize() {
return this.size;
}
// 返回映射中是否爲空的判斷值 +
isEmpty() {
return this.size === 0;
}
// 判斷當前這棵樹是不是一棵二分搜索樹,有二分搜索樹順序性
isBanarySearchTree() {
// 若是節點爲空 那麼這就是一棵空的二分搜索樹
if (!this.root) return true;
// 存儲二分搜索樹中的key
const list = new Array();
// 中序遍歷後,添加到list中的值會是以從小到大升序的樣子排列
this.inOrder(this.root, list);
// 從前日後判斷 list中的值是不是從小到大升序的排列
// 驗證 當前樹是否符合二分搜索樹的性質
for (var i = 1; i < list.length; i++)
if (list[i - 1] > list[i]) return false;
return true;
}
// 中序遍歷 輔助函數 -
inOrder(node, list) {
// 遞歸到底的狀況
if (!node) return;
// 中序遍歷時,添加到數組中的值會是以從小到大升序的樣子排列
this.inOrder(node.left, list);
list.push(node.key);
this.inOrder(node.right, list);
}
// 判斷該二叉樹是否一棵平衡二叉樹
isBalanced() {
return this.recursiveIsBalanced(this.root);
}
// 遞歸判斷某一個節點是否符合平衡二叉樹的定義 輔助函數 -
recursiveIsBalanced(node) {
// 可以遞歸到底,說明符合要求
// 空的節點左右孩子高度差確定爲0,
// 由於空樹沒有左右子樹,更加談不上下面去判斷它的左右子樹高度差是否會超過一。
if (!node) return true;
// 若是當前節點的高度差大於1 說明不符合要求
if (Math.abs(this.getBalanceFactor(node)) > 1) return false;
// 遞歸的去判斷當前節點的 左右子樹是否符合要求
return (
this.recursiveIsBalanced(node.left) &&
this.recursiveIsBalanced(node.right)
);
}
// @Override toString() 2018-11-05-jwl
toString() {
let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
// 以非遞歸的前序遍歷 輸出字符串
let stack = new MyLinkedListStack();
stack.push(this.root);
if (this.root === null) stack.pop();
while (!stack.isEmpty()) {
let node = stack.pop();
if (node.left !== null) stack.push(node.left);
if (node.right !== null) stack.push(node.right);
if (node.left === null && node.right === null) {
mapInfo += ` ${node.toString()} \r\n`;
document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
} else {
mapInfo += ` ${node.toString()}, \r\n`;
document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
}
}
mapInfo += ` ] \r\n`;
document.body.innerHTML += ` ] <br/><br/>`;
return mapInfo;
}
}
複製代碼
Main
// main 函數
class Main {
constructor() {
this.alterLine('AVLTree Area');
// 千級別
const openCount = 100; // 操做數
const rank = 10000000;
// 生成同一份測試數據的輔助代碼
const random = Math.random;
const array = new Array(openCount);
// 生成同一份測試數據
for (var i = 0; i < openCount; i++)
array[i] = Math.floor(random() * rank);
// 建立AVL樹實例
const avl = new MyAVLTree();
for (const value of array) avl.add(value);
// 輸出當前這棵avl樹是不是一個二分搜索樹
this.show('Is Binary Search Tree : ' + avl.isBanarySearchTree());
console.log('Is Binary Search Tree : ' + avl.isBanarySearchTree());
// 輸出當前這棵avl樹是不是一個平衡二叉樹
this.show('Is Balanced : ' + avl.isBalanced());
console.log('Is Balanced : ' + avl.isBalanced());
}
// 將內容顯示在頁面上
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();
};
複製代碼