什么是堆?
堆的概念:所有分支结点都大于(小于)其孩子结点的完全二叉树称为大(小)根堆。
因此,大(小)根堆的根结点必定是最大(小)元素。利用简单选择排序的思路,就可以每次从大(小)根堆中选取根结点,使元素序列有序。
思路
堆排序是一种树形选择排序方法,它的特点是将元素序列看成是一个完全二叉树的顺序存储结构,利用堆的性质对元素进行排序。
实现
void heap_sort(RceType R[], int n)
{
int i;
//构建初始堆
for (i=n/2; i>=1; i--)
sift(R, i, n);
for (i=n; i>1; i--)
{
swap(R, 1, i);
sift(R, 1, i-1);
}
}
void sift(RecType R[], int low, int high)
{
int i = low, j = 2 * i;
RecType tmp = R[i];
while (j <= high)
{
if (j < high && R[j].key < R[j+1].key)
j++;
if (tmp.key < R[j].key)
{
R[i] = R[j];
i = j;
j = 2 * i;
}
else
break;
}
R[i] = tmp;
}
算法分析
时间复杂度
假设元素序列有n个元素,那么元素序列构成的完全二叉树高度h为
⌈log2(n+1)⌉或⌊log2n⌋+1
\lceil log_2(n+1) \rceil 或 \lfloor log_2n \rfloor + 1
⌈log2(n+1)⌉或⌊log2n⌋+1
高度为h的完全二叉树对根结点进行筛选,则最多需要进行2(h-1)次比较,最多(h+1)次移动(因为比较与移动次数是同一个数量级,且移动次数较多,所以针对比较次数来分析时间复杂度)。
算法分为主要分为两步:
- 构建初始堆
- 重建堆
- 最坏时间复杂度分析
最坏的情况就是构建初始堆的时候,每个分支结点都需要且都进行最大次数的筛选。则对于第i层,某个结点作为根结点的子树的高度为
(h−i+1)
(h-i+1)
(h−i+1)
此层结点总数为
2i−1
2^{i-1}
2i−1
所以构建初始堆的最大比较次数为
∑i=h−112i−1∗2(h−i+1−1)=∑1h−12i∗(h−i)=4n
\sum _{i=h-1}^{1}2^{i-1} * 2(h-i+1-1) = \sum _1^{h-1}2^{i} * (h-i) = 4n
i=h−1∑12i−1∗2(h−i+1−1)=1∑h−12i∗(h−i)=4n
重建堆的最大比较次数为
∑i=2n2∗(⌈log2(i−1)+1⌉−1)<2nlog2n
\sum _{i=2}^n2 * (\lceil log_2{(i-1)+1} \rceil -1) < 2nlog_2n
i=2∑n2∗(⌈log2(i−1)+1⌉−1)<2nlog2n
所以堆排序的最大比较总次数(也就是最坏时间复杂度)为
4n+2nlog2n=O(nlog2n)
4n + 2nlog_2n = O(nlog_2n)
4n+2nlog2n=O(nlog2n)
堆排序和简单选择排序一样,其时间性能与初始序列的顺序无关,所以堆排序的最好、最坏和平均时间复杂度都为
O(nlog2n)
O(nlog_2n)
O(nlog2n)
空间复杂度
堆排序是一个就地排序算法,所以时间复杂度为
O(1)
O(1)
O(1)
稳定性
不稳定
适用情况
由于构建初始堆的比较次数较多,所以堆排序不适用于数据量少的情况,相反较适用于数据量大的情况