【從蛋殼到滿天飛】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
// (a) (b c)
// / \ / | \
複製代碼
// ( 42 )
// / \
// ( 17 33 ) (50)
// / | \ / \
// (6 2) (18) (37)(48)(66 88)
複製代碼
二三樹添加元素的過程總體上在二三樹中添加一個新元素的過程,面試
// 添加元素4
// ( 6, 8 ) ( 6, 8 ) ( 4, 6, 8 )
// / | \ ---> / | \ ---> / | | \
// (2,5) (7) (12) (2,4,5)(7) (12) (2) (5)(7) (12)
// (6)
// / \
// ---> (4) (8)
// / \ / \
// (2) (5) (7) (12)
//
複製代碼
學習二三樹的這種數據結構的理解,算法
紅黑樹這種數據結構本質上是和二三樹等價的數據庫
// // 二三樹
// (a) (b, c)
// / \ / \
// // 紅黑樹
// [a] [b]---[c]
// / \ / \ \
// 二分搜索樹
// {a} {c}
// / \ / \
// {b}
// / \
複製代碼
若是有興趣的話能夠本身編寫一個二三樹編程
看圖理解紅黑樹與二三樹是等價的緣由數組
[33]
的左孩子,[33]
自己在二三樹中是合在一塊兒的一個三節點,// // 二三樹中的定義 小括號中一個元素爲二節點、
// 小括號中兩個元素爲三節點。
// ( 42 )
// / \
// ( 17, 33 ) ( 50 )
// / | \ / \
// (6, 12) (18) (37) (48) (66, 88)
//
// // 紅黑樹中的定義 中括號中爲黑節點、
// 大括號中衛紅節點。
// [ 42 ]
// / \
// [ 33 ] [ 50 ]
// / \ / \
// {17} [37] [48] [88]
// / \ /
// [12] [18] {66}
// /
// {6}
// 將紅黑樹 繪製 成相似二三樹的樣子
// [ 42 ]
// / \
// {17} —— [ 33 ] [ 50 ]
// / \ \ / \
// {6} —— [12] [18] [37] [48] {66} —— [88]
//
複製代碼
實現紅黑樹只須要基於二分搜索樹映射來進行修改便可數據結構
紅黑樹中的顏色
MyRedBlackTree
// 自定義紅黑樹節點 RedBalckTreeNode
class MyRedBalckTreeNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.color = MyRedBlackTree.RED; // MyRedBlackTree.BLACK;
}
// @Override toString 2018-11-25-jwl
toString() {
return (
this.key.toString() +
'--->' +
this.value.toString() +
'--->' +
(this.color ? '紅色節點' : '綠色節點')
);
}
}
// 自定義紅黑樹 RedBlackTree
class MyRedBlackTree {
constructor() {
MyRedBlackTree.RED = true;
MyRedBlackTree.BLACK = false;
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;
}
// 根據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 MyRedBalckTreeNode(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;
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;
}
}
複製代碼
紅黑樹與二三樹是等價的
算法導論中對紅黑樹的定義
看圖理解算法導論中對紅黑樹的定義
// // 紅黑樹中的定義 中括號中爲黑節點、
// 大括號中衛紅節點。
// 將紅黑樹 繪製 成相似二三樹的樣子
// [ 42 ]
// / \
// {17} —— [ 33 ] [ 50 ]
// / \ \ / \
// {6} —— [12] [18] [37] [48] {66} —— [88]
// // 二三樹中的定義 小括號中一個元素爲二節點、
// 小括號中兩個元素爲三節點。
// ( 42 )
// / \
// ( 17, 33 ) ( 50 )
// / | \ / \
// (6, 12) (18) (37) (48) (66, 88)
複製代碼
紅黑樹是一個保持「黑平衡」的二叉樹
紅黑樹的複雜度分析
O(logn)
這個級別,O(logn)
這個級別的,O(logn)
這個級別的,O(logn)
級別的,O(logn)
這個級別的。紅黑樹對比 AVL 樹的優缺點
O(logn)
這個級別的,在通常的面試中瞭解以上基本概念以後就能夠應付大多數的面試問題
紅黑樹與二三樹是等價的
在紅黑樹中添加新節點
在紅黑樹中添加一個元素最初始的狀況
保持根節點爲黑色的節點
添加操做的狀況
左旋轉
// 原來是這樣的 ,
// 中括號爲黑色節點,大括號爲紅色節點,
// 小括號只是參與演示,並不真實存在
// [37] node
// / \
// (T1) {42} X
// / \
// (T2) (T3)
// // 進行左旋轉後
// [42] x
// / \
// node {37} (T3)
// / \
// (T1) (T2)
// // 代碼如此。
// node.right = x.left;
// x.left = node;
// // x.color = BLACK;
// x.color = node.color;
// node.color = RED;
複製代碼
MyRedBlackTree
// 自定義紅黑樹節點 RedBalckTreeNode
class MyRedBalckTreeNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.color = MyRedBlackTree.RED; // MyRedBlackTree.BLACK;
}
// @Override toString 2018-11-25-jwl
toString() {
return (
this.key.toString() +
'--->' +
this.value.toString() +
'--->' +
(this.color ? '紅色節點' : '綠色節點')
);
}
}
// 自定義紅黑樹 RedBlackTree
class MyRedBlackTree {
constructor() {
MyRedBlackTree.RED = true;
MyRedBlackTree.BLACK = false;
this.root = null;
this.size = 0;
}
// 判斷節點node的顏色
isRed(node) {
// 定義:空節點顏色爲黑色
if (!node) return MyRedBlackTree.BLACK;
return node.color;
}
// node x
// / \ 左旋轉 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
leftRotate(node) {
const x = node.right;
// 左旋轉過程
node.right = x.left;
x.left = node;
// 染色過程
x.color = node.color;
node.color = MyRedBlackTree.RED;
// 返回這個 x
return x;
}
// 比較的功能
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;
}
// 根據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);
this.root.color = MyRedBlackTree.BLACK;
}
// 添加操做 遞歸算法 -
recursiveAdd(node, key, value) {
// 解決最簡單的問題
if (node === null) {
this.size++;
return new MyRedBalckTreeNode(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;
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;
}
}
複製代碼