二叉堆和堆排序

二叉堆数据结构

二叉堆是一种特殊的二叉树,有以下两个特性:

  • 它是一颗完全二叉树,表示树的每一层都有左侧和右侧子节点(除了最后一层的叶节点),并且最后一层的叶节点尽可能都是左侧子节点,这叫作结构特性。
  • 二叉堆不是最小堆就是最大堆。最小堆允许你快速导出树的最小值,最大堆允许你快速导出树的最大值。所有的节点都大于等于(最大堆)或小于等于(最小堆)每个它的子节点。这叫作堆特性。

尽管二叉堆是二叉树,但并不一定是二叉搜索树。在二叉堆里,每个子节点都要大于等于父节点(最小堆)或小于等于父节点(最大堆)。

创建最小堆类

  1. import { defaultCompare } from './util';
  2. export class MinHeap {
  3. constructor(compareFn = defaultCompare) {
  4. this.compareFn = compareFn;
  5. this.heap = [];
  6. }
  7. }

1.二叉树的数组表示。

二叉树有两种表示方式。第一种是使用一个动态的表示方式,也就是指针。第二种是使用一个数组,通过索引值检索父节点、左侧和右侧子节点的值。

要访问使用普通数组的二叉树节点,可以用下面的方式操作index。

对于给定位置index的节点:

它的左侧子节点的位置是2*index+1;

它的右侧子节点的位置是2*index+2;

它的父节点位置是index/2。

用上面的方法访问特定节点,我们可以把下面的方法加入MinHeap类。

  1. getLeftIndex(index) {
  2. return 2 * index + 1;
  3. }
  4. getRightIndex(index) {
  5. return 2 * index + 2;
  6. }
  7. getParentIndex(index) {
  8. if(index === 0) {
  9. return undefined;
  10. }
  11. return Math.floor((index - 1) / 2);
  12. }

我们可以在堆数据结构中进行三个主要操作。

insert(value):向堆中插入一个新的值。成功返回true,否则返回false。

extract():移除最小值(最小堆)或最大值(最大堆)并返回这个值。

findMinimum():返回最小值或最大值且不会移除这个值。

  1. //向堆中插入值
  2. //指将值插入堆的底部叶节点再执行siftUp方法,表示要将这个值和它的父节点进行交换
  3. //直到父节点小于这个插入的值。
  4. insert(value) {
  5. if(value != null) {
  6. this.heap.push(value);
  7. this.siftUp(this.heap.length - 1);
  8. return true;
  9. }
  10. return false;
  11. }
  12. //上移操作
  13. siftUp(index) {
  14. let parent = this.getParentIndex(index);
  15. while(index > 0 && this.compareFn(this.heap[parent], this.heap[index]) > this.compareFn.BIGGER_THAN) {
  16. swap(this.heap, parent, index);
  17. index = parent;
  18. parent = this.getParentIndex(index);
  19. }
  20. }
  21. //从堆中找到最小值或最大值
  22. //在最小堆中,最小值总是位于数组的第一个位置
  23. size() {
  24. return this.heap.length;
  25. }
  26. isEmpty() {
  27. return this.size() === 0;
  28. }
  29. findMinimum() {
  30. return this.isEmpty() ? undefined : this.heap[0];
  31. }
  32. // 导出堆中的最小值或最大值
  33. // 移除最小值或最大值表示移除数组中的第一个元素。在移除后,我们将堆的最后一个元素移动至根部并执行siftDown函数
  34. // 表示我们将交换元素直到堆的结构正常。
  35. extract() {
  36. if(this.isEmpty()) {
  37. return undefined;
  38. }
  39. if(this.size() === 1) {
  40. return this.heap.shift();
  41. }
  42. const removedValue = this.heap.shift();
  43. this.heap[0] = this.heap.pop()
  44. this.siftDown(0);
  45. return removedValue
  46. }
  47. siftDown(index) {
  48. let element = index;
  49. const left = this.getLeftIndex(index);
  50. const right = this.getRightIndex(index);
  51. const size = this.size();
  52. if(left < size && this.compareFn(this.heap[element], this.heap[left]) > Compare.BIGGER_THAN) {
  53. element = left;
  54. }
  55. if(right < size && this.compareFn(this.heap[element], this.heap[right]) > Compare.BIGGER_THAN) {
  56. element = right;
  57. }
  58. if(index !== element) {
  59. swap(this.heap, index, element);
  60. this.siftDown(element);
  61. }
  62. }
  63. function swap(array, a, b ) {
  64. const temp = array[a];
  65. array[a] = array[b];
  66. array[b] = temp;
  67. }

堆排序算法

  1. 用数组创建一个最大堆用作数据源。
  2. 在创建最大堆后,最大的值会被存储在堆的第一个位置。我们要将它替换为堆的最后一个值,将堆的大小减1.
  3. 最后我们将堆的根节点下移并重复步骤2直到堆的大小为1。

我们用最大堆得到一个升序排列的数组,如果想要数组降序排列,可以用最小堆代替。

  1. function heapSort(array, compareFn = defaultCompare) {
  2. let heapSize = array.length;
  3. buildMaxHeap(array, compareFn);
  4. while(heapSize > 1) {
  5. swap(array, 0, --heapSize);
  6. heapify(array, 0, heapSize, compareFn);
  7. }
  8. return array;
  9. }
  10. function buildMaxHeap(array, compareFn) {
  11. for(let i = Math.floor(array.length / 2); i >= 0; i -= 1) {
  12. heapify(array, i, array.length, compareFn);
  13. }
  14. return array;
  15. }