【从蛋壳到满天飞】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();
};
复制代码