【從蛋殼到滿天飛】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 (一個一個的工程)git
所有源代碼已上傳 github,點擊我吧,光看文章可以掌握兩成,動手敲代碼、動腦思考、畫圖才能夠掌握八成。github
本文章適合 對數據結構想了解而且感興趣的人羣,文章風格一如既往如此,就以爲手機上看起來比較方便,這樣顯得比較有條理,整理這些筆記加源碼,時間跨度也算將近半年時間了,但願對想學習數據結構的人或者正在學習數據結構的人羣有幫助。面試
並查集的一個很是重要的優化 路徑壓縮算法
//// 第一種鏈接方式 的樹
// (0)
// /
// (1)
// /
// (2)
// /
// (3)
// /
//(4)
//// 第二種鏈接方式 的樹
// (0)
// / \
//(1) (2)
// / \
// (3) (4)
//// 第三種鏈接方式 的樹
// (0)
// / | \ \
//(1)(2)(3)(4)
複製代碼
路徑壓縮api
parent[p] = parent[parent[p]]
,// // 原來的樹是這個樣子
// (0)
// /
// (1)
// /
// (2)
// /
// (3)
// /
// (4)
// // 執行一次find(4) 使用了 parent[p] = parent[parent[p]]
// (0)
// /
// (1)
// |
// (2)
// / \
// (3) (4)
// // 而後再從2開始向上遍歷 再使用 parent[p] = parent[parent[p]]
// (0)
// / \
// (1) (2)
// / \
// (3) (4)
// 最後數組就是這個樣子
// 0 1 2 3 4
// -----------------
// prent 0 0 0 2 2
複製代碼
這個 rank 就是指樹的高度或樹的深度數組
(class: MyUnionFindThree, class: MyUnionFindFour, class: MyUnionFindFive, class: PerformanceTest, class: Main)
數據結構
MyUnionFindThreedom
// 自定義並查集 UnionFind 第三個版本 QuickUnion優化版
// Union 操做變快了
// 還能夠更快的
// 解決方案:考慮size 也就是某一棵樹從根節點開始一共有多少個節點
// 原理:節點少的向節點多的樹進行融合
// 還能夠更快的
class MyUnionFindThree {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 以以某個節點爲根的全部子節點的個數
this.branch = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) {
this.forest[i] = i;
this.branch[i] = 1; // 默認節點個數爲1
}
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 節點少的 樹 往 節點多的樹 進行合併,在必定程度上減小最終樹的高度
if (this.branch[primaryRoot] < this.branch[secondarRoot]) {
// 主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[secondarRoot];
// 次樹的節點個數 += 主樹的節點個數
this.branch[secondarRoot] += this.branch[primaryRoot];
} else {
// branch[primaryRoot] >= branch[secondarRoot]
// 次樹節點上往主樹節點進行合併
this.forest[secondarRoot] = this.forest[primaryRoot];
// 主樹的節點個數 += 次樹的節點個數
this.branch[primaryRoot] += this.branch[secondarRoot];
}
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(h) h 爲樹的高度
isConnected(treeQ, treeP) {
return this.find(treeQ) === this.find(treeP);
}
// 查找元素所對應的集合編號
find(id) {
if (id < 0 || id >= this.forest.length)
throw new Error('index is out of bound.');
// 不斷的去查查找當前節點的根節點
// 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
while (id !== this.forest[id]) id = this.forest[id];
return id;
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.forest.length;
}
}
複製代碼
MyUnionFindFour函數
// 自定義並查集 UnionFind 第四個版本 QuickUnion優化版
// Union 操做變快了
// 還能夠更快的
// 解決方案:考慮rank 也就是某一棵樹從根節點開始計算最大深度是多少
// 原理:讓深度比較低的那棵樹向深度比較高的那棵樹進行合併
// 還能夠更快的
class MyUnionFindFour {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 記錄某個節點爲根的樹的最大高度或深度
this.rank = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) {
this.forest[i] = i;
this.rank[i] = 1; // 默認深度爲1
}
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 根據兩個元素所在樹的rank不一樣判斷合併方向
// 將rank低的集合合併到rank高的集合上
if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
// 主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[secondarRoot];
} else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
// 次樹節點上往主樹節點進行合併
this.forest[secondarRoot] = this.forest[primaryRoot];
} else {
// rank[primaryRoot] == rank[secondarRoot]
// 若是元素個數同樣的根節點,那誰指向誰都無所謂
// 本質都是同樣的
// primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
this.forest[primaryRoot] = this.forest[secondarRoot];
this.rank[secondarRoot] += 1;
}
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(h) h 爲樹的高度
isConnected(treeQ, treeP) {
return this.find(treeQ) === this.find(treeP);
}
// 查找元素所對應的集合編號
find(id) {
if (id < 0 || id >= this.forest.length)
throw new Error('index is out of bound.');
// 不斷的去查查找當前節點的根節點
// 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
while (id !== this.forest[id]) id = this.forest[id];
return id;
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.forest.length;
}
}
複製代碼
MyUnionFindFive
// 自定義並查集 UnionFind 第五個版本 QuickUnion優化版
// Union 操做變快了
// 解決方案:考慮path compression 路徑
// 原理:在find的時候,循環遍歷操做時,讓當前節點的父節點指向它父親的父親。
// 還能夠更快的
class MyUnionFindFive {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 記錄某個節點爲根的樹的最大高度或深度
this.rank = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) {
this.forest[i] = i;
this.rank[i] = 1; // 默認深度爲1
}
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 根據兩個元素所在樹的rank不一樣判斷合併方向
// 將rank低的集合合併到rank高的集合上
if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
// 主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[secondarRoot];
} else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
// 次樹節點上往主樹節點進行合併
this.forest[secondarRoot] = this.forest[primaryRoot];
} else {
// rank[primaryRoot] == rank[secondarRoot]
// 若是元素個數同樣的根節點,那誰指向誰都無所謂
// 本質都是同樣的
// primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
this.forest[primaryRoot] = this.forest[secondarRoot];
this.rank[secondarRoot] += 1;
}
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(h) h 爲樹的高度
isConnected(treeQ, treeP) {
return this.find(treeQ) === this.find(treeP);
}
// 查找元素所對應的集合編號
find(id) {
if (id < 0 || id >= this.forest.length)
throw new Error('index is out of bound.');
// 不斷的去查查找當前節點的根節點
// 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
while (id !== this.forest[id]) {
// 進行一次節點壓縮。
this.forest[id] = this.forest[this.forest[id]];
id = this.forest[id];
}
return id;
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.forest.length;
}
}
複製代碼
PerformanceTest
// 性能測試
class PerformanceTest {
constructor() {}
// 對比隊列
testQueue(queue, openCount) {
let startTime = Date.now();
let random = Math.random;
for (var i = 0; i < openCount; i++) {
queue.enqueue(random() * openCount);
}
while (!queue.isEmpty()) {
queue.dequeue();
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 對比棧
testStack(stack, openCount) {
let startTime = Date.now();
let random = Math.random;
for (var i = 0; i < openCount; i++) {
stack.push(random() * openCount);
}
while (!stack.isEmpty()) {
stack.pop();
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 對比集合
testSet(set, openCount) {
let startTime = Date.now();
let random = Math.random;
let arr = [];
let temp = null;
// 第一遍測試
for (var i = 0; i < openCount; i++) {
temp = random();
// 添加劇復元素,從而測試集合去重的能力
set.add(temp * openCount);
set.add(temp * openCount);
arr.push(temp * openCount);
}
for (var i = 0; i < openCount; i++) {
set.remove(arr[i]);
}
// 第二遍測試
for (var i = 0; i < openCount; i++) {
set.add(arr[i]);
set.add(arr[i]);
}
while (!set.isEmpty()) {
set.remove(arr[set.getSize() - 1]);
}
let endTime = Date.now();
// 求出兩次測試的平均時間
let avgTime = Math.ceil((endTime - startTime) / 2);
return this.calcTime(avgTime);
}
// 對比映射
testMap(map, openCount) {
let startTime = Date.now();
let array = new MyArray();
let random = Math.random;
let temp = null;
let result = null;
for (var i = 0; i < openCount; i++) {
temp = random();
result = openCount * temp;
array.add(result);
array.add(result);
array.add(result);
array.add(result);
}
for (var i = 0; i < array.getSize(); i++) {
result = array.get(i);
if (map.contains(result)) map.add(result, map.get(result) + 1);
else map.add(result, 1);
}
for (var i = 0; i < array.getSize(); i++) {
result = array.get(i);
map.remove(result);
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 對比堆 主要對比 使用heapify 與 不使用heapify時的性能
testHeap(heap, array, isHeapify) {
const startTime = Date.now();
// 是否支持 heapify
if (isHeapify) heap.heapify(array);
else {
for (const element of array) heap.add(element);
}
console.log('heap size:' + heap.size() + '\r\n');
document.body.innerHTML += 'heap size:' + heap.size() + '<br /><br />';
// 使用數組取值
let arr = new Array(heap.size());
for (let i = 0; i < arr.length; i++) arr[i] = heap.extractMax();
console.log(
'Array size:' + arr.length + ',heap size:' + heap.size() + '\r\n'
);
document.body.innerHTML +=
'Array size:' +
arr.length +
',heap size:' +
heap.size() +
'<br /><br />';
// 檢驗一下是否符合要求
for (let i = 1; i < arr.length; i++)
if (arr[i - 1] < arr[i]) throw new Error('error.');
console.log('test heap completed.' + '\r\n');
document.body.innerHTML += 'test heap completed.' + '<br /><br />';
const endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 對比並查集
testUnionFind(unionFind, openCount, primaryArray, secondaryArray) {
const size = unionFind.getSize();
const random = Math.random;
return this.testCustomFn(function() {
// 合併操做
for (var i = 0; i < openCount; i++) {
let primaryId = primaryArray[i];
let secondaryId = secondaryArray[i];
unionFind.unionElements(primaryId, secondaryId);
}
// 查詢鏈接操做
for (var i = 0; i < openCount; i++) {
let primaryRandomId = Math.floor(random() * size);
let secondaryRandomId = Math.floor(random() * size);
unionFind.unionElements(primaryRandomId, secondaryRandomId);
}
});
}
// 計算運行的時間,轉換爲 天-小時-分鐘-秒-毫秒
calcTime(result) {
//獲取距離的天數
var day = Math.floor(result / (24 * 60 * 60 * 1000));
//獲取距離的小時數
var hours = Math.floor((result / (60 * 60 * 1000)) % 24);
//獲取距離的分鐘數
var minutes = Math.floor((result / (60 * 1000)) % 60);
//獲取距離的秒數
var seconds = Math.floor((result / 1000) % 60);
//獲取距離的毫秒數
var milliSeconds = Math.floor(result % 1000);
// 計算時間
day = day < 10 ? '0' + day : day;
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
milliSeconds =
milliSeconds < 100
? milliSeconds < 10
? '00' + milliSeconds
: '0' + milliSeconds
: milliSeconds;
// 輸出耗時字符串
result =
day +
'天' +
hours +
'小時' +
minutes +
'分' +
seconds +
'秒' +
milliSeconds +
'毫秒' +
' <<<<============>>>> 總毫秒數:' +
result;
return result;
}
// 自定義對比
testCustomFn(fn) {
let startTime = Date.now();
fn();
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
}
複製代碼
Main
// main 函數
class Main {
constructor() {
this.alterLine('UnionFind Comparison Area');
// 千萬級別
const size = 10000000; // 並查集維護節點數
const openCount = 10000000; // 操做數
// 生成同一份測試數據的輔助代碼
const random = Math.random;
const primaryArray = new Array(openCount);
const secondaryArray = new Array(openCount);
// 生成同一份測試數據
for (var i = 0; i < openCount; i++) {
primaryArray[i] = Math.floor(random() * size);
secondaryArray[i] = Math.floor(random() * size);
}
// 開始測試
const myUnionFindThree = new MyUnionFindThree(size);
const myUnionFindFour = new MyUnionFindFour(size);
const myUnionFindFive = new MyUnionFindFive(size);
const performanceTest = new PerformanceTest();
// 測試後獲取測試信息
const myUnionFindThreeInfo = performanceTest.testUnionFind(
myUnionFindThree,
openCount,
primaryArray,
secondaryArray
);
const myUnionFindFourInfo = performanceTest.testUnionFind(
myUnionFindFour,
openCount,
primaryArray,
secondaryArray
);
const myUnionFindFiveInfo = performanceTest.testUnionFind(
myUnionFindFive,
openCount,
primaryArray,
secondaryArray
);
// 總毫秒數:8042
console.log(
'MyUnionFindThree time:' + myUnionFindThreeInfo,
myUnionFindThree
);
this.show('MyUnionFindThree time:' + myUnionFindThreeInfo);
// 總毫秒數:7463
console.log(
'MyUnionFindFour time:' + myUnionFindFourInfo,
myUnionFindFour
);
this.show('MyUnionFindFour time:' + myUnionFindFourInfo);
// 總毫秒數:5118
console.log(
'MyUnionFindFive time:' + myUnionFindFiveInfo,
myUnionFindFive
);
this.show('MyUnionFindFive time:' + myUnionFindFiveInfo);
}
// 將內容顯示在頁面上
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();
};
複製代碼
路徑壓縮還能夠繼續優化
// // 原來的樹是這個樣子
// (0)
// /
// (1)
// /
// (2)
// /
// (3)
// /
// (4)
// 你能夠優化成這個樣子
// (0)
// / | \ \
// (1)(2)(3)(4)
// 最後數組就是這個樣子
// 0 1 2 3 4
// -----------------
// prent 0 0 0 0 0
複製代碼
非遞歸實現的路徑壓縮要比遞歸實現的路徑壓縮相對來講快一點點
find(4)
,find(4)
,find(3)
,// // 原來的樹是這個樣子
// (0)
// /
// (1)
// /
// (2)
// /
// (3)
// /
// (4)
// 優化成這個樣子了
// (0)
// / \
// (1) (2)
// / \
// (3) (4)
// 再調用一下find(4),就會變成這個樣子
// (0)
// / | \
// (1)(2) (4)
// /
// (3)
// 再調用一下find(3),就優化成這個樣子
// (0)
// / | \ \
// (1)(2)(3)(4)
// 最後數組就是這個樣子
// 0 1 2 3 4
// -----------------
// prent 0 0 0 0 0
複製代碼
(class: MyUnionFindThree, class: MyUnionFindFour, class: MyUnionFindFive,
class: MyUnionFindSix, class: PerformanceTest, class: Main)
MyUnionFindThree
// 自定義並查集 UnionFind 第三個版本 QuickUnion優化版
// Union 操做變快了
// 還能夠更快的
// 解決方案:考慮size 也就是某一棵樹從根節點開始一共有多少個節點
// 原理:節點少的向節點多的樹進行融合
// 還能夠更快的
class MyUnionFindThree {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 以以某個節點爲根的全部子節點的個數
this.branch = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) {
this.forest[i] = i;
this.branch[i] = 1; // 默認節點個數爲1
}
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 節點少的 樹 往 節點多的樹 進行合併,在必定程度上減小最終樹的高度
if (this.branch[primaryRoot] < this.branch[secondarRoot]) {
// 主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[secondarRoot];
// 次樹的節點個數 += 主樹的節點個數
this.branch[secondarRoot] += this.branch[primaryRoot];
} else {
// branch[primaryRoot] >= branch[secondarRoot]
// 次樹節點上往主樹節點進行合併
this.forest[secondarRoot] = this.forest[primaryRoot];
// 主樹的節點個數 += 次樹的節點個數
this.branch[primaryRoot] += this.branch[secondarRoot];
}
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(h) h 爲樹的高度
isConnected(treeQ, treeP) {
return this.find(treeQ) === this.find(treeP);
}
// 查找元素所對應的集合編號
find(id) {
if (id < 0 || id >= this.forest.length)
throw new Error('index is out of bound.');
// 不斷的去查查找當前節點的根節點
// 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
while (id !== this.forest[id]) id = this.forest[id];
return id;
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.forest.length;
}
}
複製代碼
MyUnionFindFour
// 自定義並查集 UnionFind 第四個版本 QuickUnion優化版
// Union 操做變快了
// 還能夠更快的
// 解決方案:考慮rank 也就是某一棵樹從根節點開始計算最大深度是多少
// 原理:讓深度比較低的那棵樹向深度比較高的那棵樹進行合併
// 還能夠更快的
class MyUnionFindFour {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 記錄某個節點爲根的樹的最大高度或深度
this.rank = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) {
this.forest[i] = i;
this.rank[i] = 1; // 默認深度爲1
}
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 根據兩個元素所在樹的rank不一樣判斷合併方向
// 將rank低的集合合併到rank高的集合上
if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
// 主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[secondarRoot];
} else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
// 次樹節點上往主樹節點進行合併
this.forest[secondarRoot] = this.forest[primaryRoot];
} else {
// rank[primaryRoot] == rank[secondarRoot]
// 若是元素個數同樣的根節點,那誰指向誰都無所謂
// 本質都是同樣的
// primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
this.forest[primaryRoot] = this.forest[secondarRoot];
this.rank[secondarRoot] += 1;
}
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(h) h 爲樹的高度
isConnected(treeQ, treeP) {
return this.find(treeQ) === this.find(treeP);
}
// 查找元素所對應的集合編號
find(id) {
if (id < 0 || id >= this.forest.length)
throw new Error('index is out of bound.');
// 不斷的去查查找當前節點的根節點
// 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
while (id !== this.forest[id]) id = this.forest[id];
return id;
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.forest.length;
}
}
複製代碼
MyUnionFindFive
// 自定義並查集 UnionFind 第五個版本 QuickUnion優化版
// Union 操做變快了
// 解決方案:考慮path compression 路徑
// 原理:在find的時候,循環遍歷操做時,讓當前節點的父節點指向它父親的父親。
// 還能夠更快的
class MyUnionFindFive {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 記錄某個節點爲根的樹的最大高度或深度
this.rank = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) {
this.forest[i] = i;
this.rank[i] = 1; // 默認深度爲1
}
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 根據兩個元素所在樹的rank不一樣判斷合併方向
// 將rank低的集合合併到rank高的集合上
if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
// 主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[secondarRoot];
} else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
// 次樹節點上往主樹節點進行合併
this.forest[secondarRoot] = this.forest[primaryRoot];
} else {
// rank[primaryRoot] == rank[secondarRoot]
// 若是元素個數同樣的根節點,那誰指向誰都無所謂
// 本質都是同樣的
// primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
this.forest[primaryRoot] = this.forest[secondarRoot];
this.rank[secondarRoot] += 1;
}
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(h) h 爲樹的高度
isConnected(treeQ, treeP) {
return this.find(treeQ) === this.find(treeP);
}
// 查找元素所對應的集合編號
find(id) {
if (id < 0 || id >= this.forest.length)
throw new Error('index is out of bound.');
// 不斷的去查查找當前節點的根節點
// 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
while (id !== this.forest[id]) {
// 進行一次節點壓縮。
this.forest[id] = this.forest[this.forest[id]];
id = this.forest[id];
}
return id;
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.forest.length;
}
}
複製代碼
MyUnionFindSix
// 自定義並查集 UnionFind 第六個版本 QuickUnion優化版
// Union 操做變快了
// 解決方案:考慮path compression 路徑
// 原理:在find的時候,循環遍歷操做時,讓全部的節點都指向根節點 以遞歸的形式進行。
// 還能夠更快的
class MyUnionFindSix {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 記錄某個節點爲根的樹的最大高度或深度
this.rank = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) {
this.forest[i] = i;
this.rank[i] = 1; // 默認深度爲1
}
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 根據兩個元素所在樹的rank不一樣判斷合併方向
// 將rank低的集合合併到rank高的集合上
if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
// 主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[secondarRoot];
} else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
// 次樹節點上往主樹節點進行合併
this.forest[secondarRoot] = this.forest[primaryRoot];
} else {
// rank[primaryRoot] == rank[secondarRoot]
// 若是元素個數同樣的根節點,那誰指向誰都無所謂
// 本質都是同樣的
// primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
this.forest[primaryRoot] = this.forest[secondarRoot];
this.rank[secondarRoot] += 1;
}
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(h) h 爲樹的高度
isConnected(treeQ, treeP) {
return this.find(treeQ) === this.find(treeP);
}
// 查找元素所對應的集合編號
find(id) {
if (id < 0 || id >= this.forest.length)
throw new Error('index is out of bound.');
// 若是當前節點不等於根節點,
// 就找到根節點而且把當前節點及以前的節點所有指向根節點
if (id !== this.forest[id])
this.forest[id] = this.find(this.forest[id]);
return this.forest[id];
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.forest.length;
}
}
複製代碼
PerformanceTest
// 性能測試
class PerformanceTest {
constructor() {}
// 對比隊列
testQueue(queue, openCount) {
let startTime = Date.now();
let random = Math.random;
for (var i = 0; i < openCount; i++) {
queue.enqueue(random() * openCount);
}
while (!queue.isEmpty()) {
queue.dequeue();
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 對比棧
testStack(stack, openCount) {
let startTime = Date.now();
let random = Math.random;
for (var i = 0; i < openCount; i++) {
stack.push(random() * openCount);
}
while (!stack.isEmpty()) {
stack.pop();
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 對比集合
testSet(set, openCount) {
let startTime = Date.now();
let random = Math.random;
let arr = [];
let temp = null;
// 第一遍測試
for (var i = 0; i < openCount; i++) {
temp = random();
// 添加劇復元素,從而測試集合去重的能力
set.add(temp * openCount);
set.add(temp * openCount);
arr.push(temp * openCount);
}
for (var i = 0; i < openCount; i++) {
set.remove(arr[i]);
}
// 第二遍測試
for (var i = 0; i < openCount; i++) {
set.add(arr[i]);
set.add(arr[i]);
}
while (!set.isEmpty()) {
set.remove(arr[set.getSize() - 1]);
}
let endTime = Date.now();
// 求出兩次測試的平均時間
let avgTime = Math.ceil((endTime - startTime) / 2);
return this.calcTime(avgTime);
}
// 對比映射
testMap(map, openCount) {
let startTime = Date.now();
let array = new MyArray();
let random = Math.random;
let temp = null;
let result = null;
for (var i = 0; i < openCount; i++) {
temp = random();
result = openCount * temp;
array.add(result);
array.add(result);
array.add(result);
array.add(result);
}
for (var i = 0; i < array.getSize(); i++) {
result = array.get(i);
if (map.contains(result)) map.add(result, map.get(result) + 1);
else map.add(result, 1);
}
for (var i = 0; i < array.getSize(); i++) {
result = array.get(i);
map.remove(result);
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 對比堆 主要對比 使用heapify 與 不使用heapify時的性能
testHeap(heap, array, isHeapify) {
const startTime = Date.now();
// 是否支持 heapify
if (isHeapify) heap.heapify(array);
else {
for (const element of array) heap.add(element);
}
console.log('heap size:' + heap.size() + '\r\n');
document.body.innerHTML += 'heap size:' + heap.size() + '<br /><br />';
// 使用數組取值
let arr = new Array(heap.size());
for (let i = 0; i < arr.length; i++) arr[i] = heap.extractMax();
console.log(
'Array size:' + arr.length + ',heap size:' + heap.size() + '\r\n'
);
document.body.innerHTML +=
'Array size:' +
arr.length +
',heap size:' +
heap.size() +
'<br /><br />';
// 檢驗一下是否符合要求
for (let i = 1; i < arr.length; i++)
if (arr[i - 1] < arr[i]) throw new Error('error.');
console.log('test heap completed.' + '\r\n');
document.body.innerHTML += 'test heap completed.' + '<br /><br />';
const endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 對比並查集
testUnionFind(unionFind, openCount, primaryArray, secondaryArray) {
const size = unionFind.getSize();
const random = Math.random;
return this.testCustomFn(function() {
// 合併操做
for (var i = 0; i < openCount; i++) {
let primaryId = primaryArray[i];
let secondaryId = secondaryArray[i];
unionFind.unionElements(primaryId, secondaryId);
}
// 查詢鏈接操做
for (var i = 0; i < openCount; i++) {
let primaryRandomId = Math.floor(random() * size);
let secondaryRandomId = Math.floor(random() * size);
unionFind.unionElements(primaryRandomId, secondaryRandomId);
}
});
}
// 計算運行的時間,轉換爲 天-小時-分鐘-秒-毫秒
calcTime(result) {
//獲取距離的天數
var day = Math.floor(result / (24 * 60 * 60 * 1000));
//獲取距離的小時數
var hours = Math.floor((result / (60 * 60 * 1000)) % 24);
//獲取距離的分鐘數
var minutes = Math.floor((result / (60 * 1000)) % 60);
//獲取距離的秒數
var seconds = Math.floor((result / 1000) % 60);
//獲取距離的毫秒數
var milliSeconds = Math.floor(result % 1000);
// 計算時間
day = day < 10 ? '0' + day : day;
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
milliSeconds =
milliSeconds < 100
? milliSeconds < 10
? '00' + milliSeconds
: '0' + milliSeconds
: milliSeconds;
// 輸出耗時字符串
result =
day +
'天' +
hours +
'小時' +
minutes +
'分' +
seconds +
'秒' +
milliSeconds +
'毫秒' +
' <<<<============>>>> 總毫秒數:' +
result;
return result;
}
// 自定義對比
testCustomFn(fn) {
let startTime = Date.now();
fn();
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
}
複製代碼
Main
// main 函數
class Main {
constructor() {
this.alterLine('UnionFind Comparison Area');
// 千萬級別
const size = 10000000; // 並查集維護節點數
const openCount = 10000000; // 操做數
// 生成同一份測試數據的輔助代碼
const random = Math.random;
const primaryArray = new Array(openCount);
const secondaryArray = new Array(openCount);
// 生成同一份測試數據
for (var i = 0; i < openCount; i++) {
primaryArray[i] = Math.floor(random() * size);
secondaryArray[i] = Math.floor(random() * size);
}
// 開始測試
const myUnionFindThree = new MyUnionFindThree(size);
const myUnionFindFour = new MyUnionFindFour(size);
const myUnionFindFive = new MyUnionFindFive(size);
const myUnionFindSix = new MyUnionFindSix(size);
const performanceTest = new PerformanceTest();
// 測試後獲取測試信息
const myUnionFindThreeInfo = performanceTest.testUnionFind(
myUnionFindThree,
openCount,
primaryArray,
secondaryArray
);
const myUnionFindFourInfo = performanceTest.testUnionFind(
myUnionFindFour,
openCount,
primaryArray,
secondaryArray
);
const myUnionFindFiveInfo = performanceTest.testUnionFind(
myUnionFindFive,
openCount,
primaryArray,
secondaryArray
);
const myUnionFindSixInfo = performanceTest.testUnionFind(
myUnionFindSix,
openCount,
primaryArray,
secondaryArray
);
// 總毫秒數:8042
console.log(
'MyUnionFindThree time:' + myUnionFindThreeInfo,
myUnionFindThree
);
this.show('MyUnionFindThree time:' + myUnionFindThreeInfo);
// 總毫秒數:7463
console.log(
'MyUnionFindFour time:' + myUnionFindFourInfo,
myUnionFindFour
);
this.show('MyUnionFindFour time:' + myUnionFindFourInfo);
// 總毫秒數:5118
console.log(
'MyUnionFindFive time:' + myUnionFindFiveInfo,
myUnionFindFive
);
this.show('MyUnionFindFive time:' + myUnionFindFiveInfo);
// 總毫秒數:5852
console.log(
'MyUnionFindSix time:' + myUnionFindSixInfo,
myUnionFindSix
);
this.show('MyUnionFindSix time:' + myUnionFindSixInfo);
}
// 將內容顯示在頁面上
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();
};
複製代碼
O(h)
,O(h)
這個級別的,O(log*n)
這個級別,這個 log*n 是另一個函數,log*
的英文叫作iterated logarithm
,log*n
在數學上有一個公式,log*n= {0 if(n<=1) || 1+log*(logn) if(n>1)}
,n<=1
的時候,log*n
爲 0,n>1
的時候,稍微有點複雜了,這是一個遞歸的定義,log*n = 1 + log*(logn)
,括號中就是對這個n
取一個log
值,log
值對應的log*
的這個是多少,logn
獲得的結果小於等於 1 了,那麼就直接獲得了 0,O(log*n)
這個級別的,log*n
這樣的時間複雜度能夠經過以上公式能夠看出,O(1)
級別的,O(1)
稍微要慢一點點,其實 logn 已是很是快的一個時間複雜度了,https://leetcode-cn.com/tag/union-find/