[小撒学算法]堆排序
2018-01-18 本文已影响0人
笨笨小撒
小撒是一只好学的小鸭子,这天,小撒在学习算法
二叉堆与最大堆
二叉堆可以被视为完全二叉树,数组和二叉堆的表现形式可以互相转换:
数组与二叉堆的转换从图中我们可以观察到二叉堆和数组的转换关系;同时也观察到,如何求一个节点的父节点、左右子节点的索引。
而在最大堆(大根堆)中,需要满足在堆中任何节点为根的子堆中,根节点存储着此堆的最大值。如上图所示的,便是一个最大堆。
调整最大堆
现在我们来考虑从两个子最大堆合并最大堆的情况。
最大堆合并调整过程假设我们要合并根节点为4
的两个最大堆[16,14,7,2,8,1]
和[10,9,,3]
,首先我们先找出4,16,10
中的最大者,并交换4
和16
的位置。因为位置的交换,使得新形成的4,14,7
可能违反了最大堆,因此需要进行判断。如此在每次交换后持续判断,直到交换至叶节点,或是新形成的子堆已经符合最大堆为止。
那怎么建立只有2或3个元素的最小堆呢?只要把其中最大的元素交换到根就行啦。
建堆
建堆的过程,便是从下至上的堆合并调整过程。这里我们需要注意到的是,非叶节点开始与Math.floor(arr.length / 2)
,我们只需让i
由此开始不断递减i
来调整堆。
堆排序(heapsort)
在建堆过程后,数组的第一个元素(堆顶)就是最大的元素,我们在这里将其与数组的最后一个元素对换后将其取出数组,然后重新建立size
减1
的最大堆,不断重复这个过程,就能一次取出数组从大至小的元素了。
代码示例(js)
// 调整堆
const maxHeapify = (arr, root) => {
let largest = root
const left = 2 * root + 1
const right = 2 * root + 2
if (left < arr.length) {
if (arr[largest] < arr[left]){
largest=left
}
if (arr[largest] < arr[right]){
largest=right
}
if (largest != root){
swap(arr, root, largest)
maxHeapify(arr, largest)
}
}
}
// 建堆
const buildMaxHeap = function (arr) {
for(let i = Math.floor(arr.length / 2) - 1; i >= 0; i--){
maxHeapify(arr, i)
}
}
// 堆排序
const heapSort = function(arr){
const rtn = []
buildMaxHeap(arr)
for (let i = arr.length - 1; i > 0; i--) {
swap(arr, 0, i)
rtn.unshift(arr.pop())
maxHeapify(arr, 0)
}
rtn.unshift(arr[0])
return rtn
}
小结
堆排序的运行时间是O(n * log(n))
,同时它是一种原地(in place)排序,只需额外开辟常数个存储空间。