什么是堆
堆是一种特殊的树。
- 堆是完全二叉树:完全二叉树,除了最后一层,其他层的节点个数都是满的,最后一层的节点靠左排列。
- 堆中每个节点的值都必须大于等于(或小于等于)其子树中每个节点的值:堆中每个节点的值都大于等于(或小于等于)其左右子节点的值。
大顶堆:每个节点的值大于等于左右子节点的值的堆
小顶堆:每个节点的值小于等于左右子节点的值的堆
实现堆
主要问题:如何存储一个堆 和 堆支持哪些操作。
数组存储堆

数组下标为 i 的几点的左节点,就是下标为 2 i 的节点,右节点是 2 i + 1 的节点。父节点是 i / 2 的节点。
插入元素
往堆中插入元素后,仍需要保持其满足堆的两个特性。
如果把新插入的元素放到堆的最后,我们需要调整这个堆,使其满足堆的特性,这个过程成为是 堆化(heapify)
堆化有从下往上和从上往下。
从下往上堆化
顺着节点所在的路径,向上比较,然后交换。
class Heap {private a : Array<number> // 数组,从下标 1开始存储数据private n : number // 堆可以存储的最大数据个数private count : number // 堆当前已经存储的数据个数public contructor(capacity: number) {this.a = new Array(capacity + 1)this.n = capacitythis.count = 0}public insert(data: number) {if (this.count > this.n) {return}this.count++this.a[this.count] = datalet i = this.countwhile( i >> 1 > 0 && this.a[i] > a[i >> 1]) {[a[i], a[i >> 1]] = [a[i >> 1], a[i]]i = i >> 1}}}
删除堆顶元素
假设我们构造的是大顶堆,堆顶是最大的元素,那么当我们删除堆顶元素之后,需要把第二大的元素放到堆顶,那么第二大的元素肯定出现在左右子节点中。然后我们就迭代地删除第二大节点即可。
那么按照这个操作可能会导致数组空洞。
从上往下堆化
升级一下思路,当删除堆顶之后,将最后一个节点放到堆顶,然后利用同样的父子节点对比的方法,对于不满足父子节点大小关系的,互换两个节点,重复这个过程,直到父子节点之间满足大小关系位置,这个过程就是从上往下堆化。
public removeMax(): number {if (this.count == 0) return -1this.a[1] = this.a[this.count]this.count --this.heapify(this.a, this.count, 1)}private heapify(a: Array<number>, n :number, i:number) {while(true) {let maxPos = iif (i*2 <= n && a[i] <a[i*2]) maxPos = i * 2if (i *2 + 1 <= n && a[maxPos] <a[i*2 + 1]) maxPos = i * 2 + 1if maxPos == i break[a[i], a[maxPos]] = [a[maxPos], a[i]]i = maxPos}}
实现堆排序
1. 建堆
数组原地建成一个堆。
思路1:按照堆中插入元素的思路建立。假设起初堆中只有一个数据,就是下标为 1 的数据,然后调用前面的插入操作,将下标为 2 到 n 的数据一次插入堆中即可。从前往后处理数组数据,从下往上堆化
思路2:从后往前处理数组数据,从上往下堆化。
这里主要讲的是思路2 。
这里的关键是,因为叶子节点是往下堆化只能自己跟自己比较,所以我们直接从最后一个非叶子节点开始堆化。
依次是 8、19、5、7

private buildHeap(a: Array<number>, n: number) {for (let i = n >> 1; i >= 1; --i) {heapify(a, n, i)}}private heapify(a: Array<number>, n :number, i:number) {while(true) {let maxPos = iif (i*2 <= n && a[i] <a[i*2]) maxPos = i * 2if (i *2 + 1 <= n && a[maxPos] <a[i*2 + 1]) maxPos = i * 2 + 1if maxPos == i break[a[i], a[maxPos]] = [a[maxPos], a[i]]i = maxPos}}
代码中我们是从 n>>1 到 1 进行堆化,也就是说下标为 n/2 + 1 到 n 的节点都是叶子节点,不需要堆化。
建堆的事件复杂度是 。
2. 排序
建堆结束后,数组中的数据已经是大顶堆了。那么数组的第一个元素就是最大元素。
这时候我们将它与最后一个元素交换,那么最大的元素就放到下标为 n 的位置了。
我们只需要把剩下 n-1 个元素堆化,构建为堆。再取堆顶元素,放到 n -1 位置。
依次直到堆中只剩下下标为 1 的一个元素,排序工作就结束了。

static public sort(a: Array<number>, n: number) {this.buildHeap(a, n)let k = nwhile (k > 1) {[a[1], a[k]] = [a[k], a[1]]k--heapify(a, k, 1)}}
时间复杂度:建堆:, 排序:
空间复杂度:原地排序算法
前面我们所有的操作都是从 下标为 1存储开始处理。
如果是下标为 0 的话,计算子节点和父节点的公式需要调整下,下标为 i 的节点,左子节点的下标是 2 * i + 1 ,右子节点 2 * i + 2 ,父节点的下标是 (i - 1) / 2
代码
class Heap {a: Array<number>; // 从 1 开始存储数据maxCount: number; // 可以存储的最大个数count: number; // 已经存储的个数constructor(capacity: number) {this.a = new Array(capacity + 1)this.maxCount = capacitythis.count = 0}public insert(data: number) {if (this.count >= this.maxCount) returnthis.count++this.a[this.count] = datalet i = this.countwhile ( i >> 1 >0 && this.a[i] > this.a[i>>1]) {[this.a[i], this.a[i>>1]] = [this.a[i>>1], this.a[i]]i = i>> 1}}public removeMax() {if (this.count == 0) returnthis.a[1] = this.a[this.count]this.count--this.heapify(this.a, this.count, 1)}private heapify(a: Array<number>, n: number, i :number) {while(true) {let maxPos:number = iif (i * 2 <= n && a[i] < a[i*2]) {maxPos = i * 2}if (i * 2 + 1 <= n && a[maxPos] < a[i*2+1]) {maxPos = i * 2}if (maxPos == i) {break}[a[i], a[maxPos]] = [a[maxPos], a[i]]i = maxPos}}private static heapify(a: Array<number>, n: number, i :number) {while(true) {let maxPos = iif (i * 2 <= n && a[i] < a[i*2]) {maxPos = i * 2}if (i * 2 + 1 <= n && a[maxPos] < a[i*2+1]){maxPos = i * 2 + 1}if (maxPos === i) {break}[a[i], a[maxPos]] = [a[maxPos], a[i]]i = maxPos}}public static buildHeap(a: Array<number>, n :number) {for (let i = n >> 1; i >= 1; i--) {Heap.heapify(a, n, i)}}public static sort(a: Array<number>, n:number) {Heap.buildHeap(a, n)let k = nwhile (k > 1) {[a[1], a[k]] = [a[k], a[1]]k--Heap.heapify(a, k, 1)}}}let arr = [0, 15,9,5,6,7,8,1,2,33, 17, 21, 16,13]// let hp = new Heap(arr.length + 1)// for (let val of arr) {// hp.insert(val)// }// console.log(hp.a)// hp.insert(22)// console.log(hp.a)// hp.removeMax()// console.log(hp.a)Heap.buildHeap(arr, 3)console.log(arr)// Heap.sort(arr,13)// console.log(arr)
package mainimport ("fmt")type heap struct {a []int // 从 1 开始count int}func buildHeap(a []int) *heap {n := len(a) -1for i:= n >> 1; i>= 1; i-- {heapify(a, n, i)}return &heap{a, n}}func sortHeap(a []int)[]int {hp := buildHeap(a)k := len(hp.a) -1for k > 1 {hp.a[1], hp.a[k] = hp.a[k], hp.a[1]k --heapify(hp.a, k, 1)}return a}func (hp *heap)Push(data int) {hp.count++hp.a = append(hp.a, data)i := hp.countfor i>> 1 > 0 && hp.a[i] > hp.a[i>>1] {hp.a[i], hp.a[i>>1] = hp.a[i>>1], hp.a[i]i = i >> 1}}func (hp *heap)Pop() int {if len(hp.a) < 1 {return -1}out := hp.a[1]hp.a[1] = hp.a[hp.count]hp.count--heapify(hp.a, hp.count, 1)return out}func heapify(a []int, n, i int) {for {maxPos := iif i * 2 <=n && a[i*2] > a[i] {maxPos = i * 2}if i * 2 +1 <= n && a[i*2+1] > a[maxPos] {maxPos = i * 2+1}if maxPos == i {break}a[i], a[maxPos] = a[maxPos], a[i]i = maxPos}}func main() {arr := []int{0, 15,9,5,6,7,8,1,2,33, 17, 21, 16,13}fmt.Println(sortHeap(arr))hp := buildHeap(arr)fmt.Println(hp.a)hp.Push(34)fmt.Println(hp.a)top :=hp.Pop()fmt.Println(top, hp.a)}
