基础
谈谈堆排序,大顶堆,小顶堆?
大顶堆:每个子节点都要大于等于父节点
小顶堆:每个子节点都要小于等于父节点
注意:区分大小顶堆和二叉搜索树的区别
构造小大顶堆
1. 创建小顶堆
二叉树有两种表示方法,(1)指针法;(2)索引法。这里我们使用索引法,即用数组表示堆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class MinHeap { constructor() { this.heap = []; } swap(nums, a, b) { [nums[a], nums[b]] = [nums[b], nums[a]]; } compare(a, b) { if (a < b) { return -1; } else if (a > b) { return 1; } else { return 0; } } }
|
2. 设置访问节点的方法
要访问使用普通数组的二叉树节点,可以通过下列方式操作index
对于给定位置index节点:
- 它的左侧节点的位置是2*index+1(如果存在)
- 它的右侧节点的位置是2*index+2(如果存在)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| getLeftIndex(index) { return 2*index+1; }
getRightIndex(index) { return 2*index+2; }
getParentIndex(index) { if (index === 0) { return undefined; } return Math.floor((index-1)/2); }
|
因此,可以实现堆中的三个操作:
- insert(value):插入值,插入成功返回true,否则返回false
- extract():移除最小值(小顶堆)或者最大值(大顶堆),并且返回这个值
- findMinimum():返回最小值(小顶堆)或者最大值(大顶堆),并且不会移除这个值
3. 插入值
插入值的操作流程:
- 将值插入堆的底部节点
- 执行siftUp方法,表示将这个值和它的父节点进行交换,直到父节点小于这个插入的值——上移操作
1 2 3 4 5 6 7 8 9
| insert(value) { if (value !== null) { this.heap.push(value); this.siftUp(this.heap.length - 1); return true; } return false; }
|
上移操作siftUp
步骤:
- 获取父节点parentIndex
- 比较节点value与父节点value的大小交换节点
- 递归,递归结束条件:index > 0 并且 this.heap[index] > this.heap[parentIndex]
1 2 3 4 5 6 7 8 9
| siftUp(index) { let parentIndex = this.getParentIndex(index); while (index > 0 && this.compare(this.heap[index], this.heap[parentIndex]) > -1) { this.swap(this.heap, index, parentIndex); index = parentIndex; parentIndex = this.getParentIndex(index); } }
|
4. 从堆中找到最小值
在最小堆中,最小值永远位于堆顶
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| size() { return this.heap.length; }
isEmpty() { return this.heap.length === 0; }
findMinimum() { return this.isEmpty ? undefined : this.heap[0]; }
|
5. 移除小顶堆中的最小值
移除值的操作流程:
- 两个特殊情况
- 堆为空,直接返回undefined
- 堆size为1,返回并移除堆顶元素,不需要其他操作
- 其他情况:
1 2 3 4 5 6 7 8 9 10 11 12
| extract() { if (this.isEmpty) { return undefined; } if (this.size() === 1) { return this.heap.shift(); } const removeValue = this.heap.shift(); this.shiftDown(0); }
|
下移操作(堆化)
整个过程可以抽象为:将堆的最后一个元素移动至根部并执行siftDown函数,执行交换函数直至堆结构正常
步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| siftDown(index) { let element = index; const left = this.getLeftIndex(index); const right = this.getRightIndex(index); const size = this.size(); if (left < size && this.compare(this.heap[element], this.heap[left]) > -1) { element = left; } if (right < size && this.compare(this.heap[element], this.heap[right]) > -1) { element = right; } if (index !== element) { this.swap(this.heap, index, element); this.siftDown(element); } }
|
构造大顶堆
大顶堆和小顶堆的算法一模一样,不同之处在于大小比较
堆排序算法
思路/步骤
- 数组构造成一个堆(根据实际需求创建大顶堆或者小顶堆),以小顶堆为例
- 在创建大顶堆后,最大值会被存储在堆的第一个位置,我们要将它替换为堆的最后一个值,将堆的大小减1
- 最后,将堆的根节点下移并且重复步骤2直到堆的大小为1
堆排序实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function heapSort(arr) { let heapSize = arr.length; buildMaxHeap(arr); while (heapSize > 1) { swap(arr, 0, --heapSize); heapify(arr, 0, heapSize); } return arr; }
function buildMaxHeap(arr) { for (let i = Math.floor(arr.length / 2); i >= 0; i--) { heapify(arr, i, arr.length); } return arr; }
|
参考
大顶堆/小顶堆的构建以及排序的应用
图解大顶堆的构建、排序过程
谈谈堆排序,大顶堆,小顶堆?