写在前面
堆排序在初学时对我来说也是比较难掌握的一种排序方法,大顶堆、小顶堆的定义能搞明白就不错了,算法在初学的时候基本是自己写不出来的。现在回过头来看一看算法描述,发现其实也不难理解,甚至还能看出来书中算法描述的小瑕疵,不过还是要记录一下,难免会忘记。
堆排序基本思想
- 将初始待排序关键字序列(R1,R2…Rn)构建成大顶堆,此堆为初始的无序区
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,…Rn-1)和新的有序区(Rn)
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,…Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2…Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成
堆排序算法的实现
实现堆排序需要解决两个问题:
- 如何将n个元素的序列按关键码建成堆。
- 输出堆顶元素后,怎么调整剩余n-1个元素,使其按关键码成为一个新堆。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
const int MAXSIZE = 100;
void heap_sort(int *, int);
void heap_adjust(int *, int, int);
int main()
{
int data[MAXSIZE];
time_t t;
srand((unsigned)time(&t));
// 随机生成20个数
for(int i=0;i<20;i++)
{
data[i] = rand() % 100;
}
for(int i=0;i<20;i++)
{
printf("%d ", data[i]);
}
printf("\n");
heap_sort(data, 20);
for(int i=0;i<20;i++)
{
printf("%d ", data[i]);
}
printf("\n");
return 0;
}
/**
* 堆排序
* @param:data 数组中第一个元素的首地址
* @param:len 数组的长度
*
*/
void heap_sort(int *data, int len)
{
// 先将数据中的元素调整成大根堆
for (int i = len / 2; i >= 0; i--)
{
heap_adjust(data, i, len - 1);
}
// 将数组第一个元素与最后一个元素交换位置,然后在调整大根堆
int tmp;
for (int i = len - 1; i > 0;i --)
{
tmp = data[i];
data[i] = data[0];
data[0] = tmp;
heap_adjust(data, 0, i - 1);
}
}
/**
* 调整成大根堆
*
* @param: data 数组中第一个元素的首地址
* @param: left 调整大根堆时的根节点的下标
* @param: right 调整大根堆时的最后一个元素的下标
*/
void heap_adjust(int *data, int left, int right)
{
int tmp = data[left];
int i = 0;
// 如果数组中,下标从0开始存储数据,那个根节点的左右子树特殊
// left == 0表示为根节点的下标,那么左子树就是1
if (left == 0)
i = 1;
else // 其余情况,左子树的下标都是当前下标乘以2
i = left * 2;
for (; i <= right; i *= 2)
{
if (i + 1 <= right && data[i] < data[i + 1])
i ++;
if (tmp > data[i]) // 经常出错的一个地方,是要为哨兵找位置,所以在比较的时候要用哨兵的值,总是写成别的
break;
data[left] = data[i];
left = i;
}
data[left] = tmp;
}
效率分析:
设树高为k,
k
=
⌊
log
2
n
⌋
+
1
k=\big\lfloor\log_2n\big\rfloor+1
k=⌊log2n⌋+1。从根节点到叶子节点的筛选,关键码比较的次数最多为2(k-1)次,交换记录最多为k次。所以在建好堆后,排序过程的删选次数不超过下式:
2
(
⌊
log
2
(
n
−
1
)
⌋
+
⌊
log
2
(
n
−
2
)
⌋
+
.
.
.
+
⌊
log
2
2
⌋
)
<
2
n
log
2
n
2(\big\lfloor\log_2(n-1)\big\rfloor+\big\lfloor\log_2(n-2)\big\rfloor+...+\big\lfloor\log_22\big\rfloor)<2n\log_2n
2(⌊log2(n−1)⌋+⌊log2(n−2)⌋+...+⌊log22⌋)<2nlog2n
而建堆时的比较次数不超过4n次,因此,堆排序在最坏情况下时间复杂度为
O
(
n
log
2
n
)
O(n\log_2n)
O(nlog2n)。
空间复杂度为
O
(
1
)
O(1)
O(1)。
堆排序属于不稳定的排序方法。