1.是一种完全二叉树,第N层至少有 2 ^(n-1)个结点
2.大顶堆:父节点 大于左右子结点;小顶堆:父节点小于左右子结点
3.用一维数组来表示
4.最后一个非叶结点的下标 = length / 2 -1
parent = floor( ( (i -1) / 2 ) i > 1
left = i * 2+1;
right = i * 2 + 2;
- 构建优先队列
- 快速找到一个集合的最大值或者最小值
- 堆排序
- 斐波那契数列,Huffman数
- 朋友面前装逼
1.shiftUp( i ):跟父结点比较,将第i个结点往上冒,将结点在数组中的更靠前,主要用于insert
2.shiftDown( i , n) : n代表当前参与构建堆的结点数量,跟左右子结点比较,与shiftUp操作相反,主要用于remove,又称为 堆化(heapify)
shiftUp 或者 shiftDown 是一个递归的过程,所以它的时间复杂度是 O(log n),这两个是堆的基本方法,其他都是基于它们产生的
3.insert( value ):将value添加到堆尾,然后调用shiftUp调整堆
4.remove( ):移除堆顶结点,然后将堆尾结点放在堆顶,调用shiftDown调整堆
// shiftUp
/**
* 往上冒 (小顶堆)
* @param arr 堆数组
* @param i 往上冒的结点下标
*/
function shiftUp(arr, i) {
if (i <= 0 || !arr) {
return;
}
// 为交换腾出坑
let tmpValue = arr[i];
while (i === 1 || Math.floor((i - 1) / 2) >= 0) {
let parent = i === 1 ? 0 : Math.floor((i - 1) / 2);
if (arr[parent] > tmpValue) {
arr[i] = arr[parent];
i = parent;
} else {
break;
}
}
arr[i] = tmpValue;
}
1.基本思想:
a.先将无序数组构建成大顶堆(小顶堆)— 从最后的一个非叶结点开始,从下往上循环调整每个小二叉树,使其形成局部最优解
b.将根结点与叶结点交换位置 — 为了下次调整堆得时候,将最大(最小)的结点排除
c.循环的重新构建一次堆 — 因为已经构成了局部最优解,这次从上往下调整,最多调整一个分支
2.选择排序,所以是不稳定的
3.O(nlogn) — 第一次构建堆,用一次for循环为O(n),以后每次循环调整 最后再遍历一个分支,即O(logn)
4.注:堆排序的主要应用并不是全排(快速排序更方便)更适用最优N解得问题,或者动态的数据;
对于已经构建好的堆,主要有两个操作,插入和删除
插入:插入到堆尾,然后从下往上调整堆
删除:将堆顶移动到堆尾,然后从上往下调整堆
// 堆排序
function heapSort(arr) {
if (!arr || typeof arr !== "object" || arr.constructor !== Array) {
return;
}
// 最后一个非叶节点的下标
let i = Math.floor(arr.length / 2) - 1;
// 构建大堆序
for (; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
// 调换堆顶和结点位置,再从上往下调整堆
for (let j = arr.length - 1; j > 0; j--) {
let temp = arr[0];
arr[0] = arr[j];
arr[j] = temp;
adjustHeap(arr, 0, j)
}
}
/**
* 调整堆结构
* 从左往右,从下到上
*/
function adjustHeap(arr, i, length) {
let temp = arr[i];
for (let k = 2 * i + 1; k < length; k = 2 * k + 1) {
if (k + 1 < length && arr[k] > arr[k + 1]) {
k++;
}
if (arr[k] < temp) {
arr[i] = arr[k];
i = k;
} else {
break;
}
}
arr[i] = temp;
}
let arr = [12, 34, 2, 34, 5, 1, 0, 2, 5, 8, 9, 6, 3, 2, 33, 3, 3, 3, 5, 6, 8];
heapSort(arr);
console.log(arr);
用JS 手动实现了堆结构,以及主要方法
/**
* 堆结构(小顶堆为例)
*/
class Heap {
constructor(arr) {
this.arr = arr;
}
get size() {
if (!this.arr || typeof this.arr !== "object" || this.arr.constructor !== Array) {
return 0;
}
return this.arr.length;
}
/**
* 将i位置的元素往上冒
* @param i
*/
shiftUp(i) {
if (this.size < 0) {
return;
}
let tempValue = this.arr[i];
while (i === 1 || Math.floor((i - 1) / 2) >= 0) {
let parent = i === 1 ? 0 : Math.floor((i - 1) / 2);
if (this.arr[parent] > tempValue) {
this.arr[i] = parent;
i = parent;
} else {
break;
}
}
this.arr[i] = tempValue;
}
/**
* 往下沉 (堆化heapify)
* @param i
*/
shiftDown(i, size) {
let length = size || this.size;
if (i < 0) {
return;
}
let tempValue = this.arr[i];
let k = i * 2 + 1;
while (k < length) {
if (k + 1 < length && this.arr[k] > this.arr[k + 1]) {
k++;
}
if (this.arr[k] < tempValue) {
this.arr[i] = this.arr[k];
i = k;
k = 2 * k + 1;
} else {
break;
}
}
this.arr[i] = tempValue;
}
/**
* 插入新元素
* 放到堆尾,然后往上冒
* @param value
*/
insert(value) {
this.arr.push(value);
this.shiftUp(this.size - 1);
}
/**
* 删除堆顶元素
* 1.移除堆顶
* 2.将堆尾的结点移到堆顶
* 3.往下沉
*/
remove() {
if (this.size <= 0) {
return;
}
if (this.size === 1) {
return this.arr.splice(0, 1);
} else {
let firstValue = this.arr[0];
this.arr[0] = this.arr[this.size - 1];
this.arr.splice(this.size - 1, 1);
this.shiftDown(0);
return firstValue;
}
}
/**
* 堆排序
* 1.先构建堆
* 2.再循环remove堆顶
* @param heap
*/
sort() {
Heap.createHeap(this);
for (let j = this.size - 1; j > 0; j--) {
let temp = this.arr[0];
this.arr[0] = this.arr[j];
this.arr[j] = temp;
this.shiftDown(0, j);
}
}
/**
* 构建堆
*
* @param heap
*/
static createHeap(heap) {
let arr = heap.arr;
if (!arr || typeof arr !== "object" || arr.constructor !== Array) {
return false;
}
//从最后一个非叶结点开上往上堆化
let node = Math.floor(arr.length / 2) - 1;
while (node >= 0) {
heap.shiftDown(node);
node--;
}
return true;
}
}
let arr = [12, 34, 2, 34, 5, 1, 0, 2, 5, 8, 9, 6, 3, 2, 33, 3, 3, 3, 5, 6, 8];
let heap = new Heap(arr);
Heap.createHeap(heap);
// heap.sort();
console.log(heap.arr);
// heap.remove();
heap.insert(-1);
console.log(heap.arr);