堆排序
HeapSort,这是相当常用的一种排序算法。原因是它的时间复杂度相当稳定,且相当高效。无论是最好情况还是最坏情况,时间复杂度都能较稳定地保持在O(nlogn)。
之所以这么高效,是因为它建立在一种特殊的数据结构上,即,堆。
什么是堆呢?其实堆就是一个完全二叉树。
因为是一个完全二叉树,所以可以方便的用顺序存储的方式存下来。再根据完全二叉树的特殊性质,可以很完美的实现堆的adjust。
关键的性质:
对于一棵有 n 个结点的完全二叉树,其节点按层序编号,每层从左到右,则对于任一结点 i 有:
- 如果 i 等于1,则节点 i 是二叉树的根,无双亲。如果 i 大于1,则该节点的双亲是结点 i / 2。
- 如果 2 * i 大于n,则该节点无左孩子,否则其左孩子是结点 2 * i 。
- 如果 2 * i + 1 大于n,则该节点无右孩子,否则其右孩子是结点 2 * i + 1 。
堆排序的关键在于adjust,即保持这颗完全二叉树是一个大顶堆(小顶堆)。
- 大顶堆:完全二叉树中,任一结点都小于其双亲结点。
- 小顶堆:完全二叉树中,任一结点都大于其双亲结点。
我们每次取下一个堆顶元素,那么只要每次操作完后,通过一次adjust,都能保证这颗完全二叉树还是一个大顶堆(小顶堆),那么我们取出的也就自然是有序序列了。
待排序数据依然存放于顺序表中。
数据存放没有从0开始,而是选择从1开始。
代码参考于《大话数据结构》。
初始设定
#include<stdio.h>
#define MAXSIZE 20 //顺序表最大容量
#define N 10 //表中数据个数
顺序表结构体
typedef struct
{
int data[MAXSIZE + 1];
int len; //已存储元素个数
}Sqlist;
输出顺序表
void Show(Sqlist L)
{
int i;
for (i = 1; i < L.len; ++i)
{
printf("%d,", L.data[i]);
}
printf("%d\n", L.data[i]);
}
输入函数
void Input(Sqlist* lp)
{
int d[N] = { 9, 1, 5, 8, 3, 0, 7, 4, 6, 2 };
for (int i = 0; i < N; i++)
lp->data[i + 1] = d[i];
lp->len = N;
}
swap函数
void Swap(Sqlist* lp,int a, int b)
{ //交换顺序表中两元素的数值
int t = lp->data[a];
lp->data[a] = lp->data[b];
lp->data[b] = t;
}
堆排序
void HeapAdjust(Sqlist* lp, int s, int m)
{
int j, temp;
temp = lp->data[s]; //暂存
for (j = 2 * s; j <= m; j *= 2)
{ //沿较大的孩子结点向下筛选
if (j < m && lp->data[j] < lp->data[j + 1])
++j; //将j标在较大的孩子上
if (temp >= lp->data[j])
break; //说明此时该子树双亲已经最大
lp->data[s] = lp->data[j]; //较大孩子上移
s = j;
}
lp->data[s] = temp; //插入
}
void HeapSort(Sqlist* lp)
{
int i;
for (i = lp->len / 2; i > 0; --i)
{ //不必考虑叶子节点,故一开始生成堆只需处理一半结点
HeapAdjust(lp, i, lp->len);
}
for (i = lp->len; i > 1; --i)
{ //每次将堆顶和最后一个元素交换,堆中元素个数减一
Swap(lp, 1, i);
HeapAdjust(lp, 1, i - 1);
}
}