数据结构——大顶堆

基础

一、 堆就是用数组实现的二叉树

堆常用方法:

  • 构建优先队列
  • 支持堆排序
  • 快速找出集合中的最小值(或最大值)

分为:最大堆和最小堆,两者差异在于节点的排序方式,在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。

二、 目的

将最大(最小)的节点放在最前面,从而进行快速的插入、删除等操作。在二叉树中搜索会很快,但是在堆中搜索会很慢。

注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。

大顶堆

  1. parent(i) = Math.floor((i - 1)/2)
  2. left(i) = 2i + 1
  3. right(i) = left + 1
Node Array index (i) Parent index Left child index Right child index
10 0 -1 1 2
7 1 0 3 4
2 2 0 5 6
5 3 1 7 8
1 4 1 9 10

具体ts实现:

  1. class MyHeap {
  2. myHeap: number[]
  3. flag: number
  4. size: number
  5. constructor(arr: number[] = [], type: string = 'MAX') {
  6. this.flag = type === 'MAX' ? 1 : -1
  7. this.myHeap = arr
  8. this.size = arr.length
  9. this.heapfiy()
  10. }
  11. /**
  12. * @description 堆化
  13. */
  14. heapfiy (): void {
  15. this.myHeap.unshift(null)
  16. const lastParentIndex = Math.floor(this.size / 2)
  17. for (let i = lastParentIndex; i > 0; i--) {
  18. this.shiftDown(this.myHeap, i, this.size)
  19. }
  20. this.myHeap.shift()
  21. }
  22. /**
  23. * @param arr 待检查数组
  24. * @param i 检查的起始下标
  25. * @param s 堆大小
  26. * @description 向下
  27. */
  28. shiftDown (arr: number[], i: number, s?: number): void {
  29. let size = s ? s : this.size
  30. let left = 2 * i
  31. let right = left + 1
  32. // 左右孩子中较大/较小的那一个
  33. let theFitOne = 0
  34. //无左右孩子节点
  35. if (left > size && right > size) {
  36. return
  37. }
  38. //只有左孩子节点
  39. if (left <= size && right > size) {
  40. theFitOne = left
  41. }
  42. //只有右孩子节点
  43. if (right <= size && left > size) {
  44. theFitOne = right
  45. }
  46. //同时有左右孩子节点
  47. if (left <= size && right <= size) {
  48. theFitOne = arr[left] * this.flag < arr[right] * this.flag ? right : left
  49. }
  50. if (arr[i] * this.flag < arr[theFitOne] * this.flag) {
  51. this.swap(arr, i, theFitOne)
  52. this.shiftDown(arr, theFitOne, size)
  53. }
  54. }
  55. /**
  56. * @param i 需要移动的元素节点索引
  57. * @description 将当前节点元素层层向上交换位置
  58. */
  59. shiftUp (arr: number[], i: number): void {
  60. const parent = Math.floor((i - 1) / 2)
  61. if (arr[i] * this.flag < arr[parent] * this.flag || parent <= 0) {
  62. return
  63. } else {
  64. this.swap(arr, i, parent)
  65. this.shiftUp(arr, parent)
  66. }
  67. }
  68. swap (arr: number[], i: number, j: number): void {
  69. let temp = arr[i]
  70. arr[i] = arr[j]
  71. arr[j] = temp
  72. }
  73. /**
  74. * @param el 插入的元素
  75. * @description 返回插入好的堆
  76. */
  77. insert (el: number): number[] {
  78. this.myHeap.push(el)
  79. const i = this.myHeap.length - 1
  80. this.shiftUp(this.myHeap, i)
  81. this.size++
  82. return this.myHeap
  83. }
  84. /**
  85. * @description 弹出最值
  86. */
  87. pop (): number {
  88. const { myHeap } = this
  89. // 交换第一个和最后一个元素
  90. this.swap(myHeap, 0, myHeap.length - 1)
  91. myHeap.unshift(null)
  92. const top = myHeap.pop()
  93. this.shiftDown(myHeap, 1, myHeap.length - 1)
  94. myHeap.shift()
  95. this.size--
  96. return top
  97. }
  98. /**
  99. * @description 返回堆排序好的新数组,不影响原数组
  100. */
  101. sort (): number[] {
  102. const { myHeap } = this
  103. const arr = [...myHeap]
  104. arr.unshift(null)
  105. for (let j = arr.length - 1; j > 1; j--) {
  106. this.swap(arr, 1, j)
  107. this.shiftDown(arr, 1, j - 1)
  108. }
  109. arr.shift()
  110. return arr
  111. }
  112. /**
  113. * @description 用删除节点就返回最大值(最大堆)或者最小值(最小堆)
  114. */
  115. peek (): number {
  116. return this.myHeap[0]
  117. }
  118. /**
  119. * @param i 被替换元素的坐标
  120. * @param el 替换的内容
  121. * @description 替换指定位置的元素
  122. */
  123. replace (i: number, el: number): void {
  124. const { myHeap } = this
  125. let orgin = myHeap[i]
  126. myHeap.unshift(null)
  127. i = i + 1
  128. myHeap.splice(i, 1, el)
  129. if ((el > orgin && this.flag > 0) || (el < orgin && this.flag < 0)) {
  130. this.shiftUp(myHeap, i)
  131. } else {
  132. this.shiftDown(myHeap, i)
  133. }
  134. myHeap.shift()
  135. }
  136. }

测试:

  1. var arr = [7,5,8,4,9,3,2,6,2]
  2. console.log(arr)
  3. var minHeap = new MyHeap([...arr], 'MIN')
  4. minHeap.peek()

leetCode

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/last-stone-weight\
 ```js
function lastStoneWeight(stones) {
const sts = new MyHeap(stones, “MAX”);
while (sts.myHeap.length > 1) {
const one = sts.pop();
const two = sts.peek();
const diff = one - two;
if (diff > 0) {
sts.replace(0, diff);
} else {
sts.pop();
}
}

  1. return sts.myHeap[0] || 0;
  2. }
  1. 执行用时:96 ms, 在所有 TypeScript 提交中击败了32.08%的用户
  2. 内存消耗:40.5 MB, 在所有 TypeScript 提交中击败了24.53%的用户
  3. = =! 还不如我之前两次提交呢
  4. ### 参考资料:
  5. - [数据结构:堆(Heap)](https://www.jianshu.com/p/6b526aa481b1)
  6. - [JS数据结构与算法之《堆》](https://zhuanlan.zhihu.com/p/144699737?utm_source=wechat_session&utm_medium=social&utm_oi=559769045767880704)