堆排序和简单选择排序同属于选择排序。
堆排序利用的是一种特殊的二叉树结构对无序序列进行排序。
堆是一种特殊的二叉树,每个子节点的值总是小于(或者大于)它的父节点,我们分别称他们为最大堆和最小堆。
由于堆是一个完全二叉树,一般情况下堆排序都是用数组的方式来实现。
堆排序的核心思想是:利用最大堆(或者最小堆)输出堆顶元素,即最大值(或最小值),将剩余元素继续生成最大堆(或最小堆),继续输出堆顶元素,重复此过程,直到全部元素都已输出得到的输出元素序列即为有序序列。
实现堆排序方法一种简单的做法是额外开辟一个辅助的数组空间,将堆顶元素逐一放入辅助数组里,最后再把辅助数组的内容复制回原始的数组。这种方法的额外空间复杂度是0(N)。下面我们讨论一种更聪明的方法,只用0(1)的额外空间即可。
如下图所示,首先将一个无序的序列生成一个最大堆,如图(a)所示。接下来我们不需要将堆顶元素输出,只要将他与堆的最后一个元素对换位置即可,如图(b)所示。这时我们确知最后一个元素99一定是递增序列的最后一个元素,而且已经在正确的位置上。
现在的问题变成了如何将剩余的元素重新生成一个最大堆——也很简单,只要依次自上而下进行过滤,使其符合最大堆性质。图(c)是调整后形成的新的最大堆。要注意的是,99已经被排除在最大堆外,即在调整的时候堆中最大元素个数应该减1,结束第1轮调整后,再次将当前堆中最后一个元素22与堆顶元素换位,如图(d)所示,再继续调整成新的最大堆... ... 如此循环直到堆中只剩1个元素,即可停止,得到一个从小到大排列的有序序列。
c6ad4532-b0ae-46e0-a44d-478064e44903.png
这个方法的时间复杂度为O(NlogN),但是不需要额外的辅助数组,所以额外空间复杂度是O(1)
算法实现(C#):
//堆排序 一种特殊结构的二叉树
public static void HeapSort(int[] arr)
{
// 建立最大堆
int i;
for (i = arr.Length / 2 - 1; i >= 0; i--)
PercDown(arr, i, arr.Length);
// 删除最大堆顶
for (i = arr.Length - 1; i > 0; i--)
{
Swap(ref arr[0], ref arr[i]);
PercDown(arr, 0, i);
}
}
//下滤操作
private static void PercDown(int[] arr, int index, int length)
{
// 将arr[index]为根的length长度个元素的堆调整为最大堆
int parent, child = 0, x;
// 取出根节点存放的值
x = arr[index];
for (parent = index; (parent * 2 + 1) < length; parent = child)
{
child = parent * 2 + 1;
// child指向左右字节点中较大者
if ((child != length - 1) && (arr[child] < arr[child + 1]))
child++;
// 找到合适位置
if (x >= arr[child])
break;
else// 下滤X
arr[parent] = arr[child];
}
arr[parent] = x;
}
(注意:Swap函数见排序序列第一篇文章)