堆(Heap)
堆排序的关键词:原地、时间复杂度堆 - 图1、排序算法

什么是堆

堆是一种特殊的树。

  • 堆是完全二叉树:完全二叉树,除了最后一层,其他层的节点个数都是满的,最后一层的节点靠左排列。
  • 堆中每个节点的值都必须大于等于(或小于等于)其子树中每个节点的值:堆中每个节点的值都大于等于(或小于等于)其左右子节点的值。

大顶堆:每个节点的值大于等于左右子节点的值的堆
小顶堆:每个节点的值小于等于左右子节点的值的堆

实现堆

主要问题:如何存储一个堆堆支持哪些操作

数组存储堆

image.png

数组下标为 i 的几点的左节点,就是下标为 2 i 的节点,右节点是 2 i + 1 的节点。父节点是 i / 2 的节点。

插入元素

往堆中插入元素后,仍需要保持其满足堆的两个特性。
image.png
如果把新插入的元素放到堆的最后,我们需要调整这个堆,使其满足堆的特性,这个过程成为是 堆化(heapify)
堆化有从下往上和从上往下。

从下往上堆化

顺着节点所在的路径,向上比较,然后交换。
image.png

  1. class Heap {
  2. private a : Array<number> // 数组,从下标 1开始存储数据
  3. private n : number // 堆可以存储的最大数据个数
  4. private count : number // 堆当前已经存储的数据个数
  5. public contructor(capacity: number) {
  6. this.a = new Array(capacity + 1)
  7. this.n = capacity
  8. this.count = 0
  9. }
  10. public insert(data: number) {
  11. if (this.count > this.n) {
  12. return
  13. }
  14. this.count++
  15. this.a[this.count] = data
  16. let i = this.count
  17. while( i >> 1 > 0 && this.a[i] > a[i >> 1]) {
  18. [a[i], a[i >> 1]] = [a[i >> 1], a[i]]
  19. i = i >> 1
  20. }
  21. }
  22. }

删除堆顶元素

假设我们构造的是大顶堆,堆顶是最大的元素,那么当我们删除堆顶元素之后,需要把第二大的元素放到堆顶,那么第二大的元素肯定出现在左右子节点中。然后我们就迭代地删除第二大节点即可。
那么按照这个操作可能会导致数组空洞。
image.png

从上往下堆化

升级一下思路,当删除堆顶之后,将最后一个节点放到堆顶,然后利用同样的父子节点对比的方法,对于不满足父子节点大小关系的,互换两个节点,重复这个过程,直到父子节点之间满足大小关系位置,这个过程就是从上往下堆化。
image.png

  1. public removeMax(): number {
  2. if (this.count == 0) return -1
  3. this.a[1] = this.a[this.count]
  4. this.count --
  5. this.heapify(this.a, this.count, 1)
  6. }
  7. private heapify(a: Array<number>, n :number, i:number) {
  8. while(true) {
  9. let maxPos = i
  10. if (i*2 <= n && a[i] <a[i*2]) maxPos = i * 2
  11. if (i *2 + 1 <= n && a[maxPos] <a[i*2 + 1]) maxPos = i * 2 + 1
  12. if maxPos == i break
  13. [a[i], a[maxPos]] = [a[maxPos], a[i]]
  14. i = maxPos
  15. }
  16. }

插入一个元素和删除堆顶元素的事件复杂度都是 堆 - 图7

实现堆排序

关键词:原地排序算法、堆 - 图8

1. 建堆

数组原地建成一个堆。
思路1:按照堆中插入元素的思路建立。假设起初堆中只有一个数据,就是下标为 1 的数据,然后调用前面的插入操作,将下标为 2 到 n 的数据一次插入堆中即可。从前往后处理数组数据,从下往上堆化
思路2:从后往前处理数组数据,从上往下堆化。

这里主要讲的是思路2 。
这里的关键是,因为叶子节点是往下堆化只能自己跟自己比较,所以我们直接从最后一个非叶子节点开始堆化。
依次是 8、19、5、7
image.pngimage.png

  1. private buildHeap(a: Array<number>, n: number) {
  2. for (let i = n >> 1; i >= 1; --i) {
  3. heapify(a, n, i)
  4. }
  5. }
  6. private heapify(a: Array<number>, n :number, i:number) {
  7. while(true) {
  8. let maxPos = i
  9. if (i*2 <= n && a[i] <a[i*2]) maxPos = i * 2
  10. if (i *2 + 1 <= n && a[maxPos] <a[i*2 + 1]) maxPos = i * 2 + 1
  11. if maxPos == i break
  12. [a[i], a[maxPos]] = [a[maxPos], a[i]]
  13. i = maxPos
  14. }
  15. }

代码中我们是从 n>>1 到 1 进行堆化,也就是说下标为 n/2 + 1n 的节点都是叶子节点,不需要堆化。
建堆的事件复杂度是 堆 - 图11

2. 排序

建堆结束后,数组中的数据已经是大顶堆了。那么数组的第一个元素就是最大元素。
这时候我们将它与最后一个元素交换,那么最大的元素就放到下标为 n 的位置了。
我们只需要把剩下 n-1 个元素堆化,构建为堆。再取堆顶元素,放到 n -1 位置。
依次直到堆中只剩下下标为 1 的一个元素,排序工作就结束了。

image.png

  1. static public sort(a: Array<number>, n: number) {
  2. this.buildHeap(a, n)
  3. let k = n
  4. while (k > 1) {
  5. [a[1], a[k]] = [a[k], a[1]]
  6. k--
  7. heapify(a, k, 1)
  8. }
  9. }

时间复杂度:建堆:堆 - 图13, 排序: 堆 - 图14
空间复杂度:原地排序算法

前面我们所有的操作都是从 下标为 1存储开始处理。
如果是下标为 0 的话,计算子节点和父节点的公式需要调整下,下标为 i 的节点,左子节点的下标是 2 * i + 1 ,右子节点 2 * i + 2 ,父节点的下标是 (i - 1) / 2

代码

TypeScript 在线地址

  1. class Heap {
  2. a: Array<number>; // 从 1 开始存储数据
  3. maxCount: number; // 可以存储的最大个数
  4. count: number; // 已经存储的个数
  5. constructor(capacity: number) {
  6. this.a = new Array(capacity + 1)
  7. this.maxCount = capacity
  8. this.count = 0
  9. }
  10. public insert(data: number) {
  11. if (this.count >= this.maxCount) return
  12. this.count++
  13. this.a[this.count] = data
  14. let i = this.count
  15. while ( i >> 1 >0 && this.a[i] > this.a[i>>1]) {
  16. [this.a[i], this.a[i>>1]] = [this.a[i>>1], this.a[i]]
  17. i = i>> 1
  18. }
  19. }
  20. public removeMax() {
  21. if (this.count == 0) return
  22. this.a[1] = this.a[this.count]
  23. this.count--
  24. this.heapify(this.a, this.count, 1)
  25. }
  26. private heapify(a: Array<number>, n: number, i :number) {
  27. while(true) {
  28. let maxPos:number = i
  29. if (i * 2 <= n && a[i] < a[i*2]) {
  30. maxPos = i * 2
  31. }
  32. if (i * 2 + 1 <= n && a[maxPos] < a[i*2+1]) {
  33. maxPos = i * 2
  34. }
  35. if (maxPos == i) {
  36. break
  37. }
  38. [a[i], a[maxPos]] = [a[maxPos], a[i]]
  39. i = maxPos
  40. }
  41. }
  42. private static heapify(a: Array<number>, n: number, i :number) {
  43. while(true) {
  44. let maxPos = i
  45. if (i * 2 <= n && a[i] < a[i*2]) {
  46. maxPos = i * 2
  47. }
  48. if (i * 2 + 1 <= n && a[maxPos] < a[i*2+1]){
  49. maxPos = i * 2 + 1
  50. }
  51. if (maxPos === i) {
  52. break
  53. }
  54. [a[i], a[maxPos]] = [a[maxPos], a[i]]
  55. i = maxPos
  56. }
  57. }
  58. public static buildHeap(a: Array<number>, n :number) {
  59. for (let i = n >> 1; i >= 1; i--) {
  60. Heap.heapify(a, n, i)
  61. }
  62. }
  63. public static sort(a: Array<number>, n:number) {
  64. Heap.buildHeap(a, n)
  65. let k = n
  66. while (k > 1) {
  67. [a[1], a[k]] = [a[k], a[1]]
  68. k--
  69. Heap.heapify(a, k, 1)
  70. }
  71. }
  72. }
  73. let arr = [0, 15,9,5,6,7,8,1,2,33, 17, 21, 16,13]
  74. // let hp = new Heap(arr.length + 1)
  75. // for (let val of arr) {
  76. // hp.insert(val)
  77. // }
  78. // console.log(hp.a)
  79. // hp.insert(22)
  80. // console.log(hp.a)
  81. // hp.removeMax()
  82. // console.log(hp.a)
  83. Heap.buildHeap(arr, 3)
  84. console.log(arr)
  85. // Heap.sort(arr,13)
  86. // console.log(arr)

go 在线地址

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type heap struct {
  6. a []int // 从 1 开始
  7. count int
  8. }
  9. func buildHeap(a []int) *heap {
  10. n := len(a) -1
  11. for i:= n >> 1; i>= 1; i-- {
  12. heapify(a, n, i)
  13. }
  14. return &heap{a, n}
  15. }
  16. func sortHeap(a []int)[]int {
  17. hp := buildHeap(a)
  18. k := len(hp.a) -1
  19. for k > 1 {
  20. hp.a[1], hp.a[k] = hp.a[k], hp.a[1]
  21. k --
  22. heapify(hp.a, k, 1)
  23. }
  24. return a
  25. }
  26. func (hp *heap)Push(data int) {
  27. hp.count++
  28. hp.a = append(hp.a, data)
  29. i := hp.count
  30. for i>> 1 > 0 && hp.a[i] > hp.a[i>>1] {
  31. hp.a[i], hp.a[i>>1] = hp.a[i>>1], hp.a[i]
  32. i = i >> 1
  33. }
  34. }
  35. func (hp *heap)Pop() int {
  36. if len(hp.a) < 1 {
  37. return -1
  38. }
  39. out := hp.a[1]
  40. hp.a[1] = hp.a[hp.count]
  41. hp.count--
  42. heapify(hp.a, hp.count, 1)
  43. return out
  44. }
  45. func heapify(a []int, n, i int) {
  46. for {
  47. maxPos := i
  48. if i * 2 <=n && a[i*2] > a[i] {
  49. maxPos = i * 2
  50. }
  51. if i * 2 +1 <= n && a[i*2+1] > a[maxPos] {
  52. maxPos = i * 2+1
  53. }
  54. if maxPos == i {
  55. break
  56. }
  57. a[i], a[maxPos] = a[maxPos], a[i]
  58. i = maxPos
  59. }
  60. }
  61. func main() {
  62. arr := []int{0, 15,9,5,6,7,8,1,2,33, 17, 21, 16,13}
  63. fmt.Println(sortHeap(arr))
  64. hp := buildHeap(arr)
  65. fmt.Println(hp.a)
  66. hp.Push(34)
  67. fmt.Println(hp.a)
  68. top :=hp.Pop()
  69. fmt.Println(top, hp.a)
  70. }