【从蛋壳到满天飞】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/