【从蛋壳到满天飞】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(logn)
级别的操做。O(logn)
级别的操做。O(logn)
级别的操做。O(n)
与O(nlogn)
的区别。(class: Myarray, class: MaxHeap, class: PerformanceTest, class: Main)
算法
Myarrayapi
// 自定义类
class MyArray {
// 构造函数,传入数组的容量capacity构造Array 默认数组的容量capacity=10
constructor(capacity = 10) {
this.data = new Array(capacity);
this.size = 0;
}
// 获取数组中的元素实际个数
getSize() {
return this.size;
}
// 获取数组的容量
getCapacity() {
return this.data.length;
}
// 判断数组是否为空
isEmpty() {
return this.size === 0;
}
// 给数组扩容
resize(capacity) {
let newArray = new Array(capacity);
for (var i = 0; i < this.size; i++) {
newArray[i] = this.data[i];
}
// let index = this.size - 1;
// while (index > -1) {
// newArray[index] = this.data[index];
// index --;
// }
this.data = newArray;
}
// 在指定索引处插入元素
insert(index, element) {
// 先判断数组是否已满
if (this.size == this.getCapacity()) {
// throw new Error("add error. Array is full.");
this.resize(this.size * 2);
}
// 而后判断索引是否符合要求
if (index < 0 || index > this.size) {
throw new Error(
'insert error. require index < 0 or index > size.'
);
}
// 最后 将指定索引处腾出来
// 从指定索引处开始,全部数组元素所有日后移动一位
// 从后往前移动
for (let i = this.size - 1; i >= index; i--) {
this.data[i + 1] = this.data[i];
}
// 在指定索引处插入元素
this.data[index] = element;
// 维护一下size
this.size++;
}
// 扩展 在数组最前面插入一个元素
unshift(element) {
this.insert(0, element);
}
// 扩展 在数组最后面插入一个元素
push(element) {
this.insert(this.size, element);
}
// 其实在数组中添加元素 就至关于在数组最后面插入一个元素
add(element) {
if (this.size == this.getCapacity()) {
// throw new Error("add error. Array is full.");
this.resize(this.size * 2);
}
// size其实指向的是 当前数组最后一个元素的 后一个位置的索引。
this.data[this.size] = element;
// 维护size
this.size++;
}
// get
get(index) {
// 不能访问没有存放元素的位置
if (index < 0 || index >= this.size) {
throw new Error('get error. index < 0 or index >= size.');
}
return this.data[index];
}
// 扩展: 获取数组中第一个元素
getFirst() {
return this.get(0);
}
// 扩展: 获取数组中最后一个元素
getLast() {
return this.get(this.size - 1);
}
// set
set(index, newElement) {
// 不能修改没有存放元素的位置
if (index < 0 || index >= this.size) {
throw new Error('set error. index < 0 or index >= size.');
}
this.data[index] = newElement;
}
// contain
contain(element) {
for (var i = 0; i < this.size; i++) {
if (this.data[i] === element) {
return true;
}
}
return false;
}
// find
find(element) {
for (var i = 0; i < this.size; i++) {
if (this.data[i] === element) {
return i;
}
}
return -1;
}
// findAll
findAll(element) {
// 建立一个自定义数组来存取这些 元素的索引
let myarray = new MyArray(this.size);
for (var i = 0; i < this.size; i++) {
if (this.data[i] === element) {
myarray.push(i);
}
}
// 返回这个自定义数组
return myarray;
}
// 删除指定索引处的元素
remove(index) {
// 索引合法性验证
if (index < 0 || index >= this.size) {
throw new Error('remove error. index < 0 or index >= size.');
}
// 暂存即将要被删除的元素
let element = this.data[index];
// 后面的元素覆盖前面的元素
for (let i = index; i < this.size - 1; i++) {
this.data[i] = this.data[i + 1];
}
this.size--;
this.data[this.size] = null;
// 若是size 为容量的四分之一时 就能够缩容了
// 防止复杂度震荡
if (Math.floor(this.getCapacity() / 4) === this.size) {
// 缩容一半
this.resize(Math.floor(this.getCapacity() / 2));
}
return element;
}
// 扩展:删除数组中第一个元素
shift() {
return this.remove(0);
}
// 扩展: 删除数组中最后一个元素
pop() {
return this.remove(this.size - 1);
}
// 扩展: 根据元素来进行删除
removeElement(element) {
let index = this.find(element);
if (index !== -1) {
this.remove(index);
}
}
// 扩展: 根据元素来删除全部元素
removeAllElement(element) {
let index = this.find(element);
while (index != -1) {
this.remove(index);
index = this.find(element);
}
// let indexArray = this.findAll(element);
// let cur, index = 0;
// for (var i = 0; i < indexArray.getSize(); i++) {
// // 每删除一个元素 原数组中就少一个元素,
// // 索引数组中的索引值是按照大小顺序排列的,
// // 因此 这个cur记录的是 原数组元素索引的偏移量
// // 只有这样才可以正确的删除元素。
// index = indexArray.get(i) - cur++;
// this.remove(index);
// }
}
// 新增: 交换两个索引位置的变量 2018-11-6
swap(indexA, indexB) {
if (
indexA < 0 ||
indexA >= this.size ||
indexB < 0 ||
indexB >= this.size
)
throw new Error('Index is Illegal.'); // 索引越界异常
let temp = this.data[indexA];
this.data[indexA] = this.data[indexB];
this.data[indexB] = temp;
}
// @Override toString 2018-10-17-jwl
toString() {
let arrInfo = `Array: size = ${this.getSize()},capacity = ${this.getCapacity()},\n`;
arrInfo += `data = [`;
for (var i = 0; i < this.size - 1; i++) {
arrInfo += `${this.data[i]}, `;
}
if (!this.isEmpty()) {
arrInfo += `${this.data[this.size - 1]}`;
}
arrInfo += `]`;
// 在页面上展现
document.body.innerHTML += `${arrInfo}<br /><br /> `;
return arrInfo;
}
}
复制代码
MaxHeap数组
// 自定义二叉堆之最大堆 Heap
class MyMaxHeap {
constructor(capacity = 10) {
this.myArray = new MyArray(capacity);
}
// 添加操做
add(element) {
// 追加元素
this.myArray.push(element);
// 将追加的元素上浮到堆中合适的位置
this.siftUp(this.myArray.getSize() - 1);
}
// 堆的上浮操做 -
siftUp(index) {
this.nonRecursiveSiftUp(index);
// this.recursiveSiftUp(index);
// 不管是递归仍是非递归都有一个
// 元素上浮后结束的条件 当前节点元素值 小于其父节点元素值
// 和
// 索引即将越界的终止条件 要上浮的元素索引 小于等于0
}
// 堆的上浮操做 递归算法 -
recursiveSiftUp(index) {
// 解决最基本的问题, 递归终止条件
if (index <= 0) return;
let currentValue = this.myArray.get(index);
let parentIndex = this.calcParentIndex(index);
let parentValue = this.myArray.get(parentIndex);
// 递归写法
if (this.compare(currentValue, parentValue) > 0) {
this.swap(index, parentIndex);
this.recursiveSiftUp(parentIndex);
}
}
// 堆的上浮操做 非递归算法 -
nonRecursiveSiftUp(index) {
if (index <= 0) return;
let currentValue = this.myArray.get(index);
let parentIndex = this.calcParentIndex(index);
let parentValue = this.myArray.get(parentIndex);
while (this.compare(currentValue, parentValue) > 0) {
// 交换堆中两个元素位置的值
this.swap(index, parentIndex);
// 交换了位置以后,元素上浮后的索引变量也要进行相应的变动
index = parentIndex;
// 若是索引小于等于0了 那就结束循环
if (index <= 0) break;
currentValue = this.myArray.get(index);
parentIndex = this.calcParentIndex(index);
parentValue = this.myArray.get(parentIndex);
}
}
// 找到优先级最大的元素 (查找元素)操做
findMax() {
if (this.myArray.isEmpty())
throw new Error('can not findMax when heap is empty.');
return this.myArray.getFirst();
}
// 提取优先级最大的元素(删除元素)操做
extractMax() {
// 获取堆顶的元素
let maxElement = this.findMax();
// 获取堆底的元素
let element = this.myArray.getLast();
// 让堆底的元素替换掉堆顶的元素
this.myArray.set(0, element);
// 移除堆底的元素
this.myArray.pop();
// 让堆顶的元素开始下沉,从而可以正常知足堆的性质
this.siftDown(0);
// 返回堆顶的元素
return maxElement;
}
// 堆的下沉操做 -
siftDown(index) {
this.nonRecursiveSiftDown(index);
// this.recursiveSiftDown(index);
}
// 堆的下沉操做 递归算法
recursiveSiftDown(index) {
// 递归终止条件
// 若是当前索引位置的元素没有左孩子就说也没有右孩子,
// 那么能够直接终止,由于没法下沉
if (this.calcLeftChildIndex(index) >= this.myArray.getSize()) return;
let leftChildIndex = this.calcLeftChildIndex(index);
let leftChildValue = this.myArray.get(leftChildIndex);
let rightChildIndex = this.calcRightChildIndex(index);
let rightChildValue = null;
// let maxIndex = 0;
// if (rightChildIndex >= this.myArray.getSize())
// maxIndex = leftChildIndex;
// else {
// rightChildValue = this.myArray.get(rightChildIndex);
// if (this.compare(rightChildValue, leftChildValue) > 0)
// maxIndex = rightChildIndex;
// else
// maxIndex = leftChildIndex;
// }
// 这段代码是上面注释代码的优化
let maxIndex = leftChildIndex;
if (rightChildIndex < this.myArray.getSize()) {
rightChildValue = this.myArray.get(rightChildIndex);
if (this.compare(leftChildValue, rightChildValue) < 0)
maxIndex = rightChildIndex;
}
let maxValue = this.myArray.get(maxIndex);
let currentValue = this.myArray.get(index);
if (this.compare(maxValue, currentValue) > 0) {
// 交换位置
this.swap(maxIndex, index);
// 继续下沉
this.recursiveSiftDown(maxIndex);
}
}
// 堆的下沉操做 非递归算法 -
nonRecursiveSiftDown(index) {
// 该索引位置的元素有左右孩子节点才能够下沉,
// 在彻底二叉树中 若是一个节点没有左孩子必然没有右孩子
while (this.calcLeftChildIndex(index) < this.myArray.getSize()) {
let leftChildIndex = this.calcLeftChildIndex(index);
let leftChildValue = this.myArray.get(leftChildIndex);
let rightChildIndex = this.calcRightChildIndex(index);
let rightChildValue = null;
let maxIndex = leftChildIndex;
if (rightChildIndex < this.myArray.getSize()) {
rightChildValue = this.myArray.get(rightChildIndex);
if (this.compare(leftChildValue, rightChildValue) < 0)
maxIndex = rightChildIndex;
}
let maxValue = this.myArray.get(maxIndex);
let currentValue = this.myArray.get(index);
if (this.compare(maxValue, currentValue) > 0) {
this.swap(maxIndex, index);
index = maxIndex;
continue;
} else break;
}
}
// 将堆顶的元素用一个新元素替换出来
replace(element) {
let maxElement = this.findMax();
this.myArray.set(0, element);
this.siftDown(0);
return maxElement;
}
// 将一个数组变成一个最大堆 -
heapify(array) {
// 将数组中的元素添加到自定义动态数组里
for (const element of array) this.myArray.push(element);
// 减小一个O(n)的操做,否则性能相对来讲会差一些
// this.myArray.data = array;
// this.myArray.size = array.length;
// 这个动态数组知足了一棵彻底二叉树的性质
// 获取 这棵彻底二叉树 最后一个非叶子节点的索引
let index = this.calcParentIndex(this.myArray.getSize() - 1);
// 从最后一个非叶子节点开始遍历 从后向前遍历 不停的下沉, 这个就是heapify的过程
// for (let i = index; i >= 0; i --) { this.siftDown(i);}
while (0 <= index) this.siftDown(index--);
}
// 堆中两个元素的位置进行交换
swap(indexA, indexB) {
this.myArray.swap(indexA, indexB);
}
// 辅助函数 计算出堆中指定索引位置的元素其父节点的索引 -
calcParentIndex(index) {
if (index === 0)
// 索引为0是根节点,根节点没有父亲节点,小于0就更加不能够了
throw new Error("index is 0. doesn't have parent.");
return Math.floor((index - 1) / 2);
}
// 辅助函数 计算出堆中指定索引位置的元素其左孩子节点的索引 -
calcLeftChildIndex(index) {
return index * 2 + 1;
}
// 辅助函数 计算出堆中指定索引位置的元素其右孩子节点的索引 -
calcRightChildIndex(index) {
return index * 2 + 2;
}
// 比较的功能 -
compare(elementA, elementB) {
if (elementA === null || elementB === null)
throw new Error("element is error. element can't compare.");
if (elementA > elementB) return 1;
else if (elementA < elementB) return -1;
else return 0;
}
// 获取堆中实际的元素个数
size() {
return this.myArray.getSize();
}
// 返回堆中元素是否为空的判断值
isEmpty() {
return this.myArray.isEmpty();
}
}
复制代码
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);
}
// 计算运行的时间,转换为 天-小时-分钟-秒-毫秒
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;
}
}
复制代码
Maindom
// main 函数
class Main {
constructor() {
this.alterLine('MaxHeap Comparison Area');
const n = 1000000;
const maxHeapIsHeapify = new MyMaxHeap();
const maxHeapNotHeapify = new MyMaxHeap();
let performanceTest1 = new PerformanceTest();
const random = Math.random;
let arr = [];
let arr1 = [];
// 循环添加随机数的值
for (let i = 0; i < n; i++) {
arr.push(random() * n);
arr1.push(arr[i]);
}
this.alterLine('MaxHeap Is Heapify Area');
const maxHeapIsHeapifyInfo = performanceTest1.testHeap(
maxHeapIsHeapify,
arr,
true
);
console.log(maxHeapIsHeapifyInfo);
this.show(maxHeapIsHeapifyInfo);
this.alterLine('MaxHeap Not Heapify Area');
const maxHeapNotHeapifyInfo = performanceTest1.testHeap(
maxHeapNotHeapify,
arr1,
false
);
console.log(maxHeapNotHeapifyInfo);
this.show(maxHeapNotHeapifyInfo);
// this.alterLine("MyMaxHeap Replace Area");
// const n = 20;
// const maxHeap = new MyMaxHeap();
// const random = Math.random;
// // 循环添加随机数的值
// for (let i = 0; i < n; i++)
// maxHeap.add(random() * n);
// console.log("MaxHeap maxHeap size:" + maxHeap.size());
// this.show("MaxHeap maxHeap size:" + maxHeap.size());
// // 使用数组取值
// let arr = [];
// for (let i = 0; i < n ; i++)
// arr[i] = maxHeap.replace(0);
// console.log("Array arr size:" + arr.length + ",MaxHeap maxHeap size:" + maxHeap.size());
// this.show("Array arr size:" + arr.length + ",MaxHeap maxHeap size:" + maxHeap.size());
// console.log(arr, maxHeap);
// // 检验一下是否符合要求
// for (let i = 1; i < n; i++)
// if (arr[i - 1] < arr[i]) throw new Error("error.");
// console.log("test maxHeap completed.");
// this.show("test maxHeap completed.");
}
// 将内容显示在页面上
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(1)
的复杂度,O(n)
的复杂度,O(n)
的复杂度,由于要找到待插入的这个元素的正确位置,O(1)
的复杂度。(class: Myarray, class: MaxHeap, class: MyPriorityQueue)
ide
Myarray
// 自定义类
class MyArray {
// 构造函数,传入数组的容量capacity构造Array 默认数组的容量capacity=10
constructor(capacity = 10) {
this.data = new Array(capacity);
this.size = 0;
}
// 获取数组中的元素实际个数
getSize() {
return this.size;
}
// 获取数组的容量
getCapacity() {
return this.data.length;
}
// 判断数组是否为空
isEmpty() {
return this.size === 0;
}
// 给数组扩容
resize(capacity) {
let newArray = new Array(capacity);
for (var i = 0; i < this.size; i++) {
newArray[i] = this.data[i];
}
// let index = this.size - 1;
// while (index > -1) {
// newArray[index] = this.data[index];
// index --;
// }
this.data = newArray;
}
// 在指定索引处插入元素
insert(index, element) {
// 先判断数组是否已满
if (this.size == this.getCapacity()) {
// throw new Error("add error. Array is full.");
this.resize(this.size * 2);
}
// 而后判断索引是否符合要求
if (index < 0 || index > this.size) {
throw new Error(
'insert error. require index < 0 or index > size.'
);
}
// 最后 将指定索引处腾出来
// 从指定索引处开始,全部数组元素所有日后移动一位
// 从后往前移动
for (let i = this.size - 1; i >= index; i--) {
this.data[i + 1] = this.data[i];
}
// 在指定索引处插入元素
this.data[index] = element;
// 维护一下size
this.size++;
}
// 扩展 在数组最前面插入一个元素
unshift(element) {
this.insert(0, element);
}
// 扩展 在数组最后面插入一个元素
push(element) {
this.insert(this.size, element);
}
// 其实在数组中添加元素 就至关于在数组最后面插入一个元素
add(element) {
if (this.size == this.getCapacity()) {
// throw new Error("add error. Array is full.");
this.resize(this.size * 2);
}
// size其实指向的是 当前数组最后一个元素的 后一个位置的索引。
this.data[this.size] = element;
// 维护size
this.size++;
}
// get
get(index) {
// 不能访问没有存放元素的位置
if (index < 0 || index >= this.size) {
throw new Error('get error. index < 0 or index >= size.');
}
return this.data[index];
}
// 扩展: 获取数组中第一个元素
getFirst() {
return this.get(0);
}
// 扩展: 获取数组中最后一个元素
getLast() {
return this.get(this.size - 1);
}
// set
set(index, newElement) {
// 不能修改没有存放元素的位置
if (index < 0 || index >= this.size) {
throw new Error('set error. index < 0 or index >= size.');
}
this.data[index] = newElement;
}
// contain
contain(element) {
for (var i = 0; i < this.size; i++) {
if (this.data[i] === element) {
return true;
}
}
return false;
}
// find
find(element) {
for (var i = 0; i < this.size; i++) {
if (this.data[i] === element) {
return i;
}
}
return -1;
}
// findAll
findAll(element) {
// 建立一个自定义数组来存取这些 元素的索引
let myarray = new MyArray(this.size);
for (var i = 0; i < this.size; i++) {
if (this.data[i] === element) {
myarray.push(i);
}
}
// 返回这个自定义数组
return myarray;
}
// 删除指定索引处的元素
remove(index) {
// 索引合法性验证
if (index < 0 || index >= this.size) {
throw new Error('remove error. index < 0 or index >= size.');
}
// 暂存即将要被删除的元素
let element = this.data[index];
// 后面的元素覆盖前面的元素
for (let i = index; i < this.size - 1; i++) {
this.data[i] = this.data[i + 1];
}
this.size--;
this.data[this.size] = null;
// 若是size 为容量的四分之一时 就能够缩容了
// 防止复杂度震荡
if (Math.floor(this.getCapacity() / 4) === this.size) {
// 缩容一半
this.resize(Math.floor(this.getCapacity() / 2));
}
return element;
}
// 扩展:删除数组中第一个元素
shift() {
return this.remove(0);
}
// 扩展: 删除数组中最后一个元素
pop() {
return this.remove(this.size - 1);
}
// 扩展: 根据元素来进行删除
removeElement(element) {
let index = this.find(element);
if (index !== -1) {
this.remove(index);
}
}
// 扩展: 根据元素来删除全部元素
removeAllElement(element) {
let index = this.find(element);
while (index != -1) {
this.remove(index);
index = this.find(element);
}
// let indexArray = this.findAll(element);
// let cur, index = 0;
// for (var i = 0; i < indexArray.getSize(); i++) {
// // 每删除一个元素 原数组中就少一个元素,
// // 索引数组中的索引值是按照大小顺序排列的,
// // 因此 这个cur记录的是 原数组元素索引的偏移量
// // 只有这样才可以正确的删除元素。
// index = indexArray.get(i) - cur++;
// this.remove(index);
// }
}
// 新增: 交换两个索引位置的变量 2018-11-6
swap(indexA, indexB) {
if (
indexA < 0 ||
indexA >= this.size ||
indexB < 0 ||
indexB >= this.size
)
throw new Error('Index is Illegal.'); // 索引越界异常
let temp = this.data[indexA];
this.data[indexA] = this.data[indexB];
this.data[indexB] = temp;
}
// @Override toString 2018-10-17-jwl
toString() {
let arrInfo = `Array: size = ${this.getSize()},capacity = ${this.getCapacity()},\n`;
arrInfo += `data = [`;
for (var i = 0; i < this.size - 1; i++) {
arrInfo += `${this.data[i]}, `;
}
if (!this.isEmpty()) {
arrInfo += `${this.data[this.size - 1]}`;
}
arrInfo += `]`;
// 在页面上展现
document.body.innerHTML += `${arrInfo}<br /><br /> `;
return arrInfo;
}
}
复制代码
MaxHeap
// 自定义二叉堆之最大堆 Heap
class MyMaxHeap {
constructor(capacity = 10) {
this.myArray = new MyArray(capacity);
}
// 添加操做
add(element) {
// 追加元素
this.myArray.push(element);
// 将追加的元素上浮到堆中合适的位置
this.siftUp(this.myArray.getSize() - 1);
}
// 堆的上浮操做 -
siftUp(index) {
this.nonRecursiveSiftUp(index);
// this.recursiveSiftUp(index);
// 不管是递归仍是非递归都有一个
// 元素上浮后结束的条件 当前节点元素值 小于其父节点元素值
// 和
// 索引即将越界的终止条件 要上浮的元素索引 小于等于0
}
// 堆的上浮操做 递归算法 -
recursiveSiftUp(index) {
// 解决最基本的问题, 递归终止条件
if (index <= 0) return;
let currentValue = this.myArray.get(index);
let parentIndex = this.calcParentIndex(index);
let parentValue = this.myArray.get(parentIndex);
// 递归写法
if (this.compare(currentValue, parentValue) > 0) {
this.swap(index, parentIndex);
this.recursiveSiftUp(parentIndex);
}
}
// 堆的上浮操做 非递归算法 -
nonRecursiveSiftUp(index) {
if (index <= 0) return;
let currentValue = this.myArray.get(index);
let parentIndex = this.calcParentIndex(index);
let parentValue = this.myArray.get(parentIndex);
while (this.compare(currentValue, parentValue) > 0) {
// 交换堆中两个元素位置的值
this.swap(index, parentIndex);
// 交换了位置以后,元素上浮后的索引变量也要进行相应的变动
index = parentIndex;
// 若是索引小于等于0了 那就结束循环
if (index <= 0) break;
currentValue = this.myArray.get(index);
parentIndex = this.calcParentIndex(index);
parentValue = this.myArray.get(parentIndex);
}
}
// 找到优先级最大的元素 (查找元素)操做
findMax() {
if (this.myArray.isEmpty())
throw new Error('can not findMax when heap is empty.');
return this.myArray.getFirst();
}
// 提取优先级最大的元素(删除元素)操做
extractMax() {
// 获取堆顶的元素
let maxElement = this.findMax();
// 获取堆底的元素
let element = this.myArray.getLast();
// 让堆底的元素替换掉堆顶的元素
this.myArray.set(0, element);
// 移除堆底的元素
this.myArray.pop();
// 让堆顶的元素开始下沉,从而可以正常知足堆的性质
this.siftDown(0);
// 返回堆顶的元素
return maxElement;
}
// 堆的下沉操做 -
siftDown(index) {
this.nonRecursiveSiftDown(index);
// this.recursiveSiftDown(index);
}
// 堆的下沉操做 递归算法
recursiveSiftDown(index) {
// 递归终止条件
// 若是当前索引位置的元素没有左孩子就说也没有右孩子,
// 那么能够直接终止,由于没法下沉
if (this.calcLeftChildIndex(index) >= this.myArray.getSize()) return;
let leftChildIndex = this.calcLeftChildIndex(index);
let leftChildValue = this.myArray.get(leftChildIndex);
let rightChildIndex = this.calcRightChildIndex(index);
let rightChildValue = null;
// let maxIndex = 0;
// if (rightChildIndex >= this.myArray.getSize())
// maxIndex = leftChildIndex;
// else {
// rightChildValue = this.myArray.get(rightChildIndex);
// if (this.compare(rightChildValue, leftChildValue) > 0)
// maxIndex = rightChildIndex;
// else
// maxIndex = leftChildIndex;
// }
// 这段代码是上面注释代码的优化
let maxIndex = leftChildIndex;
if (rightChildIndex < this.myArray.getSize()) {
rightChildValue = this.myArray.get(rightChildIndex);
if (this.compare(leftChildValue, rightChildValue) < 0)
maxIndex = rightChildIndex;
}
let maxValue = this.myArray.get(maxIndex);
let currentValue = this.myArray.get(index);
if (this.compare(maxValue, currentValue) > 0) {
// 交换位置
this.swap(maxIndex, index);
// 继续下沉
this.recursiveSiftDown(maxIndex);
}
}
// 堆的下沉操做 非递归算法 -
nonRecursiveSiftDown(index) {
// 该索引位置的元素有左右孩子节点才能够下沉,
// 在彻底二叉树中 若是一个节点没有左孩子必然没有右孩子
while (this.calcLeftChildIndex(index) < this.myArray.getSize()) {
let leftChildIndex = this.calcLeftChildIndex(index);
let leftChildValue = this.myArray.get(leftChildIndex);
let rightChildIndex = this.calcRightChildIndex(index);
let rightChildValue = null;
let maxIndex = leftChildIndex;
if (rightChildIndex < this.myArray.getSize()) {
rightChildValue = this.myArray.get(rightChildIndex);
if (this.compare(leftChildValue, rightChildValue) < 0)
maxIndex = rightChildIndex;
}
let maxValue = this.myArray.get(maxIndex);
let currentValue = this.myArray.get(index);
if (this.compare(maxValue, currentValue) > 0) {
this.swap(maxIndex, index);
index = maxIndex;
continue;
} else break;
}
}
// 将堆顶的元素用一个新元素替换出来
replace(element) {
let maxElement = this.findMax();
this.myArray.set(0, element);
this.siftDown(0);
return maxElement;
}
// 将一个数组变成一个最大堆 -
heapify(array) {
// 将数组中的元素添加到自定义动态数组里
for (const element of array) this.myArray.push(element);
// 减小一个O(n)的操做,否则性能相对来讲会差一些
// this.myArray.data = array;
// this.myArray.size = array.length;
// 这个动态数组知足了一棵彻底二叉树的性质
// 获取 这棵彻底二叉树 最后一个非叶子节点的索引
let index = this.calcParentIndex(this.myArray.getSize() - 1);
// 从最后一个非叶子节点开始遍历 从后向前遍历 不停的下沉, 这个就是heapify的过程
// for (let i = index; i >= 0; i --) { this.siftDown(i);}
while (0 <= index) this.siftDown(index--);
}
// 堆中两个元素的位置进行交换
swap(indexA, indexB) {
this.myArray.swap(indexA, indexB);
}
// 辅助函数 计算出堆中指定索引位置的元素其父节点的索引 -
calcParentIndex(index) {
if (index === 0)
// 索引为0是根节点,根节点没有父亲节点,小于0就更加不能够了
throw new Error("index is 0. doesn't have parent.");
return Math.floor((index - 1) / 2);
}
// 辅助函数 计算出堆中指定索引位置的元素其左孩子节点的索引 -
calcLeftChildIndex(index) {
return index * 2 + 1;
}
// 辅助函数 计算出堆中指定索引位置的元素其右孩子节点的索引 -
calcRightChildIndex(index) {
return index * 2 + 2;
}
// 比较的功能 -
compare(elementA, elementB) {
if (elementA === null || elementB === null)
throw new Error("element is error. element can't compare.");
if (elementA > elementB) return 1;
else if (elementA < elementB) return -1;
else return 0;
}
// 获取堆中实际的元素个数
size() {
return this.myArray.getSize();
}
// 返回堆中元素是否为空的判断值
isEmpty() {
return this.myArray.isEmpty();
}
}
复制代码
MyPriorityQueue
// 自定义优先队列 PriorityQueue
class MyPriorityQueue {
constructor() {
this.maxHeap = new MyMaxHeap();
}
// 入队
enqueue(element) {
this.maxHeap.add(element);
}
// 出队
dequeue() {
return this.maxHeap.extractMax();
}
// 查看队首元素
getFront() {
return this.maxHeap.findMax();
}
// 查看队列中实际元素的个数
getSize() {
return this.maxHeap.size();
}
// 返回队列是否为空的判断值
isEmpty() {
return this.maxHeap.isEmpty();
}
// 扩展: 修改最大堆中的比较算法
updateCompare(compareMethod) {
// 传入参数能够替换掉原堆中实现的compare方法
this.maxHeap.compare = compareMethod;
}
// 扩展: 用一个新元素去替换队首的元素,同时再次确认优先级别
replaceFront(element) {
// 这样就就可 不须要 出队入队操做这么麻烦了
return this.maxHeap.replace(element);
}
}
复制代码
1,000,000
个元素中选出前 100 名?
NlogN
的时间里完成任务,NlogM
这个时间复杂度内解决问题,NlogN
这个时间复杂度内的话,347.前K个高频元素
https://leetcode-cn.com/problems/top-k-frequent-elements/
,代码示例
// 答题
class Solution {
// leetcode 347. 前K个高频元素
topKFrequent(nums, k) {
/** * @param {number[]} nums * @param {number} k * @return {number[]} * 原版 */
var topKFrequent = function(nums, k) {
let map = new Map();
// 统计 数组中每个元素出现频率
for (const num of nums) {
if (map.has(num)) map.set(num, map.get(num) + 1);
else map.set(num, 1);
}
// 优先队列:使用的时候指定优先级比较的方式
let queue = new MyPriorityQueue();
// 变动优先队列中的定义优先级的方法
queue.updateCompare((elementA, elementB) => {
// 原的比较算法是 值越大 优先级越大
// 如今改成 值越小 优先级越大
if (elementA.value < elementB.value) return 1;
else if (elementA.value > elementB.value) return -1;
else return 0;
});
for (const key of map.keys()) {
if (queue.getSize() < k)
queue.enqueue({ key: key, value: map.get(key) });
else if (map.get(key) > queue.getFront().value) {
queue.replaceFront({ key: key, value: map.get(key) });
// queue.dequeue();
// queue.enqueue({"key": key, "value": map.get(key)});
}
}
let result = [];
for (var i = 0; i < k; i++) {
result.push(queue.dequeue().key);
}
return result;
};
// 精简版
var topKFrequent = function(nums, k) {
let map = new Map();
// 统计 数组中每个元素出现频率
for (const num of nums) {
if (map.has(num)) map.set(num, map.get(num) + 1);
else map.set(num, 1);
}
// 优先队列:使用的时候指定优先级比较的方式
let queue = new MyPriorityQueue();
// 变动优先队列中的定义优先级的方法
queue.updateCompare((keyA, keyB) => {
// 原的比较算法是 值越大 优先级越大
// 如今改成 值越小 优先级越大
if (map.get(keyA) < map.get(keyB)) return 1;
else if (map.get(keyA) > map.get(keyB)) return -1;
else return 0;
});
for (const key of map.keys()) {
if (queue.getSize() < k) queue.enqueue(key);
else if (map.get(key) > map.get(queue.getFront())) {
queue.replaceFront(key);
}
}
let result = [];
for (var i = 0; i < k; i++) {
result.push(queue.dequeue());
}
return result;
};
return topKFrequent(nums, k);
}
}
// main 函数
class Main {
constructor() {
this.alterLine('leetcode 347. 前K个高频元素');
let s = new Solution();
let arr = [
5,
-3,
9,
1,
7,
7,
9,
10,
2,
2,
10,
10,
3,
-1,
3,
7,
-9,
-1,
3,
3
];
console.log(arr);
this.show(arr);
let result = s.topKFrequent(arr, 3);
console.log(result);
this.show(result);
}
// 将内容显示在页面上
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();
};
复制代码
https://leetcode-cn.com/tag/heap/
索引堆
这样的数据结构,索引堆
仍是应用很是普遍的一种数据结构,Queue
void enqueue(e)
E dequeue()
E getFront()
int getSize()
boolean isEmpty()