在《算法导论》第3版第6章讲解了堆排序算法
与归并排序一样,但不同于插入排序的是,堆排序的时间复杂度是O(nlogn)。
而与插入排序相同,但不同于归并排序的是,堆排序同样具有空间原址性:任何时候都只需要常数个额外的元素空间存储临时数据。
因此,堆排序是集合了归并排序、插入排序优点的一种排序算法。
堆排序引入了一种算法设计技巧:使用一种称为“堆”的数据结构来进行信息管理。
堆不仅用在堆排序中,而且它也可以构造一种有效的优先队列。
虽然“堆”这一词源自堆排序,但是目前它已经被引申为“垃圾收集存储机制”,例如在Java和Lisp中所定义的。强调一下,我们使用的堆指的是堆数据结构,而不是垃圾收集存储。
堆排序的过程:
1:初始时候,利用 build_max_heap 将输入数组 A[0..n-1] 建成最大堆
2:因为数组中的最大元素总在根结点 A[0] 中,把它与 A[n-1] 互换,从而让该元素放到正确的位置。
3:这时候,从堆中去掉结点 n-1(–heap_size),剩余的结点中,原来根的孩子结点仍然是最大堆,而新的根结点
可能会违背最大堆的性质。为了维护最大堆的性质,我们要做的是调用 max_heapify(A,i),从而在 A[0..n-2] 上构造一个新的最大堆。
4:不断重复这一过程,直到堆的大小从n-1降到2
代码如下
#include <iostream>
using namespace std;
int heap_size = 0;
// 父结点下标
int parent(int i)
{
return (i - 1) / 2;
}
// 左孩子下标
int left(int i)
{
return 2 * i + 1;
}
// 右孩子下标
int right(int i)
{
return 2 * i + 2;
}
// max_heapify 通过让 A[i] 的值在最大堆中“逐级下降”,从而使得以下标 i 为根结点的子树重新遵循最大堆的性质
void max_heapify(int A[], int i)
{
int l = left(i);
int r = right(i);
int largest = i;
if (l < heap_size && A[l] > A[i])
largest = l;
if (r < heap_size && A[r] > A[largest])
largest = r;
if (largest != i)
{
swap(A[i], A[largest]);
max_heapify(A, largest);
}
}
void max_heapify_iterative(int A[], int i)
{
bool flag = true;
while (i < heap_size && flag)
{
int l = left(i);
int r = right(i);
int largest = i;
if (l < heap_size && A[l] > A[i])
largest = l;
if (r < heap_size && A[r] > A[largest])
largest = r;
if (largest != i)
{
swap(A[i], A[largest]);
i = largest;
}
else
flag = false;
}
}
// 建堆,用自底向上的方法利用过程 max_heapify 把一个大小为 len 的数组 A[0..len-1] 转换为最大堆
void build_max_heap(int A[], int len)
{
heap_size = len;
for (int i = (len - 1) / 2; i >= 0; --i)
{
max_heapify(A, i);
// max_heapify_iterative(A, i);
}
}
void heapsort(int A[], int len)
{
build_max_heap(A, len);
for (int i = len - 1; i >= 1; --i)
{
swap(A[0], A[i]);
--heap_size;
max_heapify(A, 0);
}
}
int main()
{
int A[] = { 100, -3, 10, -1, 0, 1000, 200 };
int len = sizeof(A) / sizeof(A[0]);
if (len <= 0)
return 0;
heapsort(A, len);
for (int i = 0; i < len; ++i)
cout << A[i] << endl;
return 0;
}