【從蛋殼到滿天飛】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
本文章適合 對數據結構想了解而且感興趣的人羣,文章風格一如既往如此,就以爲手機上看起來比較方便,這樣顯得比較有條理,整理這些筆記加源碼,時間跨度也算將近半年時間了,但願對想學習數據結構的人或者正在學習數據結構的人羣有幫助。算法
並
其實就是集合中的並
這樣的概念,O(n)
union(p, q)
,也就是並的操做,傳入兩個參數 p 和 q,isConnected(p, q)
,MyUnionFind
unionElements(p, q)
:將這兩個數據以及他們所在的集合進行合併。isConnected(p, q)
:查詢兩個數據是否在同一個集合中。getSize()
:當前並查集一共考慮多少個元素MyUnionFind編程
// 自定義並查集 UnionFind
class MyUnionFind {
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
unionElements(q, p) {}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
isConnected(q, p) {}
// 功能:當前並查集一共考慮多少個元素
getSize() {}
}
複製代碼
對於並查集主要實現兩個操做api
並查集的基本數據表示數組
// 並查集 一
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// id 0 0 0 0 0 1 1 1 1 1
// 並查集 二
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// id 0 1 0 1 0 1 0 1 0 1
複製代碼
使用 id 這樣的一個數組來存儲你的數據微信
find(p)
是否等於find(q)
就行了。當你使用 find 函數進行操做的時候只須要O(1)
的時間複雜度網絡
// 並查集
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// id 0 1 0 1 0 1 0 1 0 1
複製代碼
QuickFind 方式的並查集中實現 union數據結構
union(1, 4)
,union(1, 4)
後的並查集,O(n)
,// 並查集
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// id 0 1 0 1 0 1 0 1 0 1
// 並查集 union(1, 4)以後的並查集
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// id 1 1 1 1 1 1 1 1 1 1
複製代碼
MyUnionFindOne
// 自定義並查集 UnionFind 第一個版本 QuickFind版
// isConnected 操做很快
class MyUnionFindOne {
constructor(size) {
// 存儲數據所對應的集合的編號
this.ids = new Array(size);
// 模擬存入數據
const len = this.ids.length;
for (var i = 0; i < len; i++) this.ids[i] = i;
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(n)
unionElements(q, p) {
const qId = this.find(q);
const pId = this.find(p);
if (qId === pId) return;
for (var i = 0; i < this.ids.length; i++)
if (pId === this.ids[i]) this.ids[i] = qId;
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(1)
isConnected(q, p) {
return this.ids[q] === this.ids[p];
}
// 查找元素所對應的集合編號
find(index) {
if (index < 0 || index >= this.ids.length)
throw new Error('index is out of bound.');
return this.ids[index];
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.ids.length;
}
}
複製代碼
QuickFind 的方式實現的並查集查找速度很是快
QuickUnion 的方式實現並查集思路
// (5) (2)
// / \ | \
// / \ | \
// (6) (7) (3) (1)
複製代碼
QuickUnion 的方式實現並查集很是的簡單
parent[i] = i
,union(4, 3)
操做,parent[4] = 3
,union(3, 8)
操做,parent[3] = 8
,再進行union(6, 5)
操做,parent[6] = 5
,再進行union(9, 4)
操做,parent[9] = 8
,之因此不讓節點 9 指向節點 4,parent[9] = 8
,再進行union(2, 1)
操做,parent[2] = 1
,union(5, 0)
操做,直接讓節點 5 指向節點 0 就行了,parent[5] = 0
,再進行union(7, 2)
操做,parent[7] = 1
。union(6, 2)
操做,parent[0] = 1
。// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// parent 0 1 2 3 4 5 6 7 8 9
//
// Quick Union
// (0) (1) (2) (3) (4) (5) (6) (7) (8) (9)
//
// 一通以下操做
// union(4, 3); // 4->3
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// 0 1 2 3 3 5 6 7 8 9
//
// union(3, 8); // 3->8
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// 0 1 2 8 3 5 6 7 8 9
//
// union(6, 5); // 6->5
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// 0 1 2 8 3 5 5 7 8 9
//
// union(9, 4); // 4->3 3->8 因此 9->8
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// 0 1 2 8 3 5 5 7 8 8
//
// union(2, 1); // 2->1
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// 0 1 1 8 3 5 5 7 8 8
//
// union(5, 0); // 5->0
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// 0 1 1 8 3 0 5 7 8 8
//
// union(7, 2); // 2->1 因此 7->1
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// 0 1 1 8 3 0 5 1 8 8
//
// union(6, 2); // 6->5 5->0,2->1 因此0->1
// 0 1 2 3 4 5 6 7 8 9
// -------------------------------------
// 1 1 1 8 3 0 5 1 8 8
複製代碼
QuickUnion 的方式實現並查集中的 union 操做的時間複雜度是O(h)
這個版本的並查集雖然是使用數組來進行存儲的
MyUnionFindTwo
// 自定義並查集 UnionFind 第二個版本 QuickUnion版
// Union 操做變快了
// 還能夠更快的
class MyUnionFindTwo {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) this.forest[i] = i;
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 不管哪棵樹往那棵樹上進行合併 都同樣,他們都是樹
// 這裏是主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[secondarRoot];
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(h) h 爲樹的高度
isConnected(treeQ, treeP) {
return this.find(treeQ) === this.find(treeP);
}
// 查找元素所對應的集合編號
find(id) {
if (id < 0 || id >= this.ids.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.ids.length;
}
}
複製代碼
O(1)
級別的,O(n)
級別的。O(h)
級別的,O(h)
級別的。O(h)
級別的,O(h)
級別的複雜度還比O(n)
級別的複雜度還要慢一些,O(h)
的時間複雜度來講,h 越小它的時間複雜就會越小,(class: MyUnionFindOne, class: MyUnionFindTwo, class: MyUnionFindThree, class: PerformanceTest, class: Main)
MyUnionFindOne
// 自定義並查集 UnionFind 第一個版本 QuickFind版
// isConnected 操做很快
class MyUnionFindOne {
constructor(size) {
// 存儲數據所對應的集合的編號
this.ids = new Array(size);
// 模擬存入數據
const len = this.ids.length;
for (var i = 0; i < len; i++) this.ids[i] = i;
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(n)
unionElements(q, p) {
const qId = this.find(q);
const pId = this.find(p);
if (qId === pId) return;
for (var i = 0; i < this.ids.length; i++)
if (pId === this.ids[i]) this.ids[i] = qId;
}
// 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
// 時間複雜度:O(1)
isConnected(q, p) {
return this.ids[q] === this.ids[p];
}
// 查找元素所對應的集合編號
find(index) {
if (index < 0 || index >= this.ids.length)
throw new Error('index is out of bound.');
return this.ids[index];
}
// 功能:當前並查集一共考慮多少個元素
getSize() {
return this.ids.length;
}
}
複製代碼
MyUnionFindTwo
// 自定義並查集 UnionFind 第二個版本 QuickUnion版
// Union 操做變快了
// 還能夠更快的
class MyUnionFindTwo {
constructor(size) {
// 存儲當前節點所指向的父節點
this.forest = new Array(size);
// 在初始的時候每個節點都指向它本身
// 也就是每個節點都是獨立的一棵樹
const len = this.forest.length;
for (var i = 0; i < len; i++) this.forest[i] = i;
}
// 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
// 時間複雜度:O(h) h 爲樹的高度
unionElements(treePrimary, treeSecondary) {
const primaryRoot = this.find(treePrimary);
const secondarRoot = this.find(treeSecondary);
if (primaryRoot === secondarRoot) return;
// 不管哪棵樹往那棵樹上進行合併 都同樣,他們都是樹
// 這裏是主樹節點上往次樹節點進行合併
this.forest[primaryRoot] = this.forest[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;
}
}
複製代碼
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;
}
}
複製代碼
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 = 100000; // 並查集維護節點數
const openCount = 100000; // 操做數
// 生成同一份測試數據的輔助代碼
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 myUnionFindOne = new MyUnionFindOne(size);
const myUnionFindTwo = new MyUnionFindTwo(size);
const myUnionFindThree = new MyUnionFindThree(size);
const performanceTest = new PerformanceTest();
// 測試後獲取測試信息
const myUnionFindOneInfo = performanceTest.testUnionFind(
myUnionFindOne,
openCount,
primaryArray,
secondaryArray
);
const myUnionFindTwoInfo = performanceTest.testUnionFind(
myUnionFindTwo,
openCount,
primaryArray,
secondaryArray
);
const myUnionFindThreeInfo = performanceTest.testUnionFind(
myUnionFindThree,
openCount,
primaryArray,
secondaryArray
);
// 總毫秒數:24143
console.log(
'MyUnionFindOne time:' + myUnionFindOneInfo,
myUnionFindOne
);
this.show('MyUnionFindOne time:' + myUnionFindOneInfo);
// 總毫秒數:32050
console.log(
'MyUnionFindTwo time:' + myUnionFindTwoInfo,
myUnionFindTwo
);
this.show('MyUnionFindTwo time:' + myUnionFindTwoInfo);
// 總毫秒數:69
console.log(
'MyUnionFindThree time:' + myUnionFindThreeInfo,
myUnionFindThree
);
this.show('MyUnionFindThree time:' + myUnionFindThreeInfo);
}
// 將內容顯示在頁面上
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();
};
複製代碼
rank[i]
表示根節點爲 i 的樹的高度是多少。(class: MyUnionFindThree, class: MyUnionFindFour, 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;
}
}
複製代碼
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 performanceTest = new PerformanceTest();
// 測試後獲取測試信息
const myUnionFindThreeInfo = performanceTest.testUnionFind(
myUnionFindThree,
openCount,
primaryArray,
secondaryArray
);
const myUnionFindFourInfo = performanceTest.testUnionFind(
myUnionFindFour,
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);
}
// 將內容顯示在頁面上
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();
};
複製代碼