堆
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);