优先队列的实现比较

实现 插入 删除 查找最大/最小关键字 缺点
数组 1 n n -
链表 1 n 1 -
有序数组 n 或 logn n 1 -
有序链表 n 1 1 -
二叉搜索树 logn logn logn 多次删除后容易导致树倾斜
二叉堆 logn logn 1

二叉堆

image.png

二叉堆的性质

  • 节点 k 的父节点下标为 k / 2(向下取整)
  • 已某节点为根节点的子树,该节点是这颗树的极值

二叉堆的操作

插入

二叉堆的插入非常简单,只需要在二叉堆的最后添加要插入的内容,并将其“上浮”到正确位置。
尝试在上面的二叉堆中插入新元素9,过程如下:

image.png
在尾部插入元素9,与父节点进行对比,有序性被破坏,与父元素替换位置。

image.png
替换成功后,继续上一轮操作,与父节点进行对比,仍然无法满足有序性,继续调换位置。
image.png

再次替换后符合。

实现

  1. function push(heap: Heap, node: Node): void {
  2. const index = heap.length;
  3. heap.push(node); // 在堆尾部添加元素
  4. siftUp(heap, node, index); // 进行上浮操作
  5. }
  6. function siftUp(heap, node, i) {
  7. let index = i;
  8. while (true) {
  9. const parentIndex = (index - 1) >>> 1; // 父节点位置: parentIndex = childIndex / 2
  10. const parent = heap[parentIndex];
  11. if (parent !== undefined && compare(parent, node) > 0) {
  12. // The parent is larger. Swap positions.
  13. heap[parentIndex] = node;
  14. heap[index] = parent;
  15. index = parentIndex;
  16. } else {
  17. // The parent is smaller. Exit.
  18. return;
  19. }
  20. }
  21. }

删除

取出根节点的值对比插入稍微复杂一点,归纳起来可以分为三步:

  1. 取出根节点的值。
  2. 将最后一个元素与根节点进行替换,并删除最后一个元素。
  3. 下沉;

image.png
取出根节点。
image.png
将最后一个元素与根节点调换,并删除。对比发现有序性被破坏,进行对调。
image.png
完成删除。

程序框架

  1. function pop {
  2. * 设定 minItem 保存根节点
  3. * 取出最后一个节点与根节点替换,并删除最后一个节点
  4. * 执行下沉循环
  5. * 将根元素与左右子节点对比,挑选较小的与父节点替换位置
  6. return minItem
  7. }

实现

  1. export function pop(heap: Heap): Node | null {
  2. const first = heap[0]; // 取出根节点
  3. if (first !== undefined) {
  4. const last = heap.pop(); // 取出最后一位元素,并删除
  5. if (last !== first) {
  6. heap[0] = last; // 与根节点对调
  7. siftDown(heap, last, 0); // 下沉
  8. }
  9. return first;
  10. } else {
  11. return null;
  12. }
  13. }
  14. function siftDown(heap, node, i) {
  15. let index = i;
  16. const length = heap.length;
  17. while (index < length) {
  18. const leftIndex = (index + 1) * 2 - 1;
  19. const left = heap[leftIndex];
  20. const rightIndex = leftIndex + 1;
  21. const right = heap[rightIndex];
  22. // If the left or right node is smaller, swap with the smaller of those.
  23. // 寻找左右儿子较小的那一个替换
  24. if (left !== undefined && compare(left, node) < 0) { //左子节点小于根节点
  25. if (right !== undefined && compare(right, left) < 0) {
  26. heap[index] = right;
  27. heap[rightIndex] = node;
  28. index = rightIndex;
  29. } else {
  30. heap[index] = left;
  31. heap[leftIndex] = node;
  32. index = leftIndex;
  33. }
  34. } else if (right !== undefined && compare(right, node) < 0) { // 左子接点大于根节点,右子节点小于根节点
  35. heap[index] = right;
  36. heap[rightIndex] = node;
  37. index = rightIndex;
  38. } else {
  39. // Neither child is smaller. Exit.
  40. return;
  41. }
  42. }
  43. }

以下是 react 源码中 scheduler/src/SchedulerMinHeap.js 关于最小堆的完整实现:

  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @flow strict
  8. */
  9. type Heap = Array<Node>;
  10. type Node = {|
  11. id: number,
  12. sortIndex: number,
  13. |};
  14. export function push(heap: Heap, node: Node): void {
  15. const index = heap.length;
  16. heap.push(node);
  17. siftUp(heap, node, index);
  18. }
  19. export function peek(heap: Heap): Node | null {
  20. const first = heap[0];
  21. return first === undefined ? null : first;
  22. }
  23. export function pop(heap: Heap): Node | null {
  24. const first = heap[0];
  25. if (first !== undefined) {
  26. const last = heap.pop();
  27. if (last !== first) {
  28. heap[0] = last;
  29. siftDown(heap, last, 0);
  30. }
  31. return first;
  32. } else {
  33. return null;
  34. }
  35. }
  36. function siftUp(heap, node, i) {
  37. let index = i;
  38. while (true) {
  39. const parentIndex = (index - 1) >>> 1; // 父节点位置: parentIndex = childIndex / 2
  40. const parent = heap[parentIndex];
  41. if (parent !== undefined && compare(parent, node) > 0) {
  42. // The parent is larger. Swap positions.
  43. heap[parentIndex] = node;
  44. heap[index] = parent;
  45. index = parentIndex;
  46. } else {
  47. // The parent is smaller. Exit.
  48. return;
  49. }
  50. }
  51. }
  52. function siftDown(heap, node, i) {
  53. let index = i;
  54. const length = heap.length;
  55. while (index < length) {
  56. const leftIndex = (index + 1) * 2 - 1;
  57. const left = heap[leftIndex];
  58. const rightIndex = leftIndex + 1;
  59. const right = heap[rightIndex];
  60. // If the left or right node is smaller, swap with the smaller of those.
  61. if (left !== undefined && compare(left, node) < 0) { //
  62. if (right !== undefined && compare(right, left) < 0) {
  63. heap[index] = right;
  64. heap[rightIndex] = node;
  65. index = rightIndex;
  66. } else {
  67. heap[index] = left;
  68. heap[leftIndex] = node;
  69. index = leftIndex;
  70. }
  71. } else if (right !== undefined && compare(right, node) < 0) {
  72. heap[index] = right;
  73. heap[rightIndex] = node;
  74. index = rightIndex;
  75. } else {
  76. // Neither child is smaller. Exit.
  77. return;
  78. }
  79. }
  80. }
  81. function compare(a, b) {
  82. // Compare sort index first, then task id.
  83. const diff = a.sortIndex - b.sortIndex;
  84. return diff !== 0 ? diff : a.id - b.id;
  85. }