堆通常是一颗完全二叉树的数组对象,是非线性数据结构
堆通常有最大堆和最小堆两个分类

最大堆: 每个根节点的值都大于子节点
最小堆: 每个根节点的值都小于子节点

堆本质上一种二叉树,但使用的是数组形式

  1. /*
  2. 80
  3. 29 38
  4. 15 6 20 10
  5. 3 4
  6. */
  7. // 表示为数组的形式
  8. var arr = [80, 29, 38, 15, 6, 20, 10, 3,4]

按照二叉树的每一排依次进行序号的排序,图中从上到下索引
如果节点不是最后一个子节点,那么这个节点必须包含左右两个子节点

节点索引的计算
对于序号为k的节点,它的左节点为 2k + 1, 右节点为 2k+2, 父节点为 (k - 1) / 2

堆的操作
堆化,维护最大、最小堆的过程,和子节点不断比较,将最大、最小值和父节点交换

实现堆

  1. /*
  2. 80
  3. 29 38
  4. 15 6 20 10
  5. 3 4
  6. */
  7. // 表示为数组的形式
  8. var arr = [80, 29, 38, 15, 6, 20, 10, 3,4]
  9. class MaxHeap {
  10. constructor(initArray, maxSize = 9999) {
  11. this.arr = initArray || []
  12. this.currSize = this.arr.length;
  13. this.maxSize = maxSize
  14. this.heap = new Array(this.arr.length)
  15. this.init()
  16. }
  17. init() {
  18. for (let i = 0; i < this.currSize; i++) {
  19. this.heap[i] = this.arr[i]
  20. }
  21. //如果initDataArray非空,那么需要对数据进行堆化。若initDataArray为空,此步可以省略
  22. // 获取最后一个分支节点的父节点的索引
  23. let curPos = Math.floor((this.curSize - 2) / 2)
  24. while(curPos >= 0) {
  25. // 局部的自上往下下滑调整
  26. this.upToDown(curPos, this.currSize - 1)
  27. curPos--;
  28. }
  29. }
  30. // 得到栈结构数组
  31. getHeap() {
  32. return this.heap;
  33. }
  34. // 由上至下堆化数据
  35. upToDown(start, m) {
  36. let parentIndex = start;
  37. // 左节点的值
  38. let maxChildIndex = parentIndex *2 + 1;
  39. while(maxChildIndex <= m) {
  40. // 如果右节点的值 > 左节点的值
  41. if (maxChildIndex < m && this.heap[maxChildIndex] < this.heap[maxChildIndex + 1]) {
  42. maxChildIndex = maxChildIndex + 1;
  43. }
  44. // 如果父节点值的大于子节点的值,不需要交换
  45. if (this.heap[parentIndex] >= this.heap[maxChildIndex]) {
  46. break
  47. } else {
  48. // 否则,交换父子节点的值
  49. let temp = this.heap[parentIndex]
  50. this.heap[parentIndex] = this.heap[maxChildIndex]
  51. this.heap[maxChildIndex] = temp;
  52. parentIndex = maxChildIndex
  53. maxChildIndex = maxChildIndex * 2 + 1
  54. }
  55. }
  56. }
  57. insert(data) {
  58. // 当前容量大小等于最大容量,返回插入失败
  59. if (this.currSize == this.maxSize) return false;
  60. this.heap[this.currSize] = data;
  61. // 由下至上堆化数据
  62. this.downToUp(this.currSize);
  63. this.currSize++;
  64. return true;
  65. }
  66. // 由下至上堆化数据
  67. downToUp(start) {
  68. let childIndex = start;
  69. let parentIndex = Math.floor((childIndex - 1) / 2);
  70. while(childIndex > 0) {
  71. // 如果大就不交换
  72. if(this.heap[parentIndex] >= this.heap[childIndex]) {
  73. break
  74. } else {
  75. // 否则,交换父子节点的值
  76. let temp = this.heap[parentIndex]
  77. this.heap[parentIndex] = this.heap[childIndex]
  78. this.heap[childIndex] = temp;
  79. childIndex = parentIndex
  80. parentIndex = Math.floor((parentIndex - 1) / 2)
  81. }
  82. }
  83. }
  84. // 移除根元素并返回
  85. removeRoot() {
  86. if (this.currSize <= 0) return null;
  87. let maxValue = this.heap[0]
  88. this.heap[0] = this.heap[this.currSize]
  89. this.currSize--;
  90. // 重新堆化数据
  91. this.upToDown(0, this.currSize - 1);
  92. return maxValue;
  93. }
  94. }
  95. const maxHeap = new MaxHeap()
  96. const initArray = [1,2,3,4,5]
  97. for(let i = 0; i < 5; i++) {
  98. maxHeap.insert(initArray[i])
  99. }
  100. console.log(maxHeap.getHeap()) // [5,4,2,1,3]
  101. console.log('max1:', maxHeap.removeRoot()) // 5
  102. console.log(maxHeap.getHeap()) // [5,4,2,1,3]
  103. console.log('max2:', maxHeap.removeRoot()) // 4
  104. console.log(maxHeap.getHeap()) // [5,4,2,1,3]
  105. console.log('max3:', maxHeap.removeRoot()) // 3
  106. console.log(maxHeap.getHeap()) // [5,4,2,1,3]

小结

  1. 堆的条件和生成堆的几个条件
  2. 节点的索引计算
  3. 堆的操作

练习题

报数。
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:

  1. 1. 1
  2. 2. 11
  3. 3. 21
  4. 4. 1211
  5. 5. 111221
  6. 1 被读作 "one 1" ("一个一") , 11
  7. 11 被读作 "two 1s" ("两个一"), 21
  8. 21 被读作 "one 2", "one 1" "一个二" , "一个一") , 1211
  9. 给定一个正整数 n1 n 30),输出报数序列的第 n 项。
  10. 输入: 1
  11. 输出: "1"
  12. 输入: 4
  13. 输出: "1211"
  1. function countAndSay(n) {
  2. if ( n === 1) {
  3. return '1'
  4. }
  5. let origin = 1;
  6. let text = '';
  7. let html = '';
  8. while(origin <n) {
  9. if(!html) {
  10. html = `${origin}`
  11. }
  12. let arr = []
  13. let data = ''
  14. for (let i = 0, len = html.length; i <len; i++) {
  15. // 将字符中相同的数字做统计
  16. if (!data) {
  17. data += html[i]
  18. } else {
  19. // 如果碰到不同的数字,则将data清空,重新计算
  20. if (html[i] !== data[data.length - 1]) {
  21. arr.push(data)
  22. data = ''
  23. }
  24. data += html[i]
  25. }
  26. }
  27. arr.push(data)
  28. text = ''
  29. arr.forEach(item => {
  30. text += `${item.length}${item[0]}`
  31. html = text
  32. })
  33. origin++;
  34. }
  35. return text;
  36. }