堆排序
时间复杂度:O(nlog2n)
空间复杂度:O(1)
稳定性:不稳定
适合情况:堆排序适合待排序数据较多的情况
堆,它是一棵完全二叉树,且只对父节点和儿子节点的大小加以规定,而对左儿子和右儿子节点的大小关系不要求。
(大根堆):如果一个节点有儿子节点,则此节点数据必须大于或等于其儿子节点数据。(小根堆与之相反)
由于堆是完全二叉树,采用将节点顺序编号存入一维数组中的顺序存储表示比链接法表示节省存储空间,也便于计算。
由堆(大根堆)的定义,可知,其根节点(数组中下标为最小的节点)数据最大,堆排序便是利用这一点。由此,实现堆排序需要解决两个问题:
(1) 对一组待排序数据,先将它们构建成一个堆,输出堆顶的最大数据。
(2) 将余下的n-1个数据再构建堆,输出具有次小值的数据;如此反复进行下去,直至全部数据都输出,就可以得到排好序的元素序列。
构建堆:堆排序的关键是构建堆,一般构建堆是采用一种称为筛选(sift)的算法。
首先把待排序数据按任意次序置入完全二叉树的各节点中,接下来用筛选运算进行调整。筛选运算从最末尾节点的父节点开始,向前逐节点进行(如图,按照4、3、2、1的顺序),直至筛选完根节点即形成堆。
筛每个节点的操作是:将该节点数值与其两个儿子节点中数值较大者进行比较,若小于较大的儿子节点的数值,则与之交换,互换后,又将换到儿子节点位置的节点作为父节点,再与下一层的儿子节点进行比较,如此下去,直至满足父节点都大于等于其儿子节点(这是筛选一个节点的操作)。
下图是构建完成一个大根堆。
///构建堆(筛选)算法
#include <stdio.h>
#define MAXITEM 100
typedef int KeyType; //定义关键字类型
typedef char ElemType[5]; //说明ElemType等同于char[5]类型
typedef struct Record
{
KeyType key;
ElemType data;
}SeqList[MAXITEM]; //SeqList是struct Record[MAXITEM]类型
//筛选第V号节点的函数如下:
void Sift(SeqList R,int v,int n) //n为数据元素的个数,v为要调整节点
{
int i,j;
i=v;
j=2*i;
R[0]=R[i]; //R[0]为辅助空间
while(j<=n)
{
if(j<n&&R[j].key<R[j+1].key)
j++; //找到最大的儿子节点
if(R[j].key>R[0].key) //若儿子节点大于父节点
{
R[i]=R[j]; //将儿子节点放到父节点位置
i=j; //继续以被交换的儿子节点为父节点继续比较
j=2*i;
}
else
j=n+1; //筛选完成,使循环终止
}
R[i]=R[0];//被筛节点放入最终位置
}
令待筛节点下标v由⌊n/2⌋逐次减小到1,反复调用Sift函数,就可以得到符合条件的堆。
利用堆进行排序:由于堆中根节点数据总是所有数据中最大的,利用堆进行排序的算法是从根节点逐个取出数据。取出根节点,然后将最后的节点提到根节点,继续调整堆,继续取出根节点数据,直到堆中只剩下一个节点时,此时待排序记录已是按要求排好序的了。
为节省存储空间,排序得到的有序数据序列仍存放于原数组中,将从根节点取出的数据由数组的末端起逐单元存放。每存放一个数据,同时将原来在该单元的数据换到根节点,然后令v=1再调用一次Sift函数。如此进行下去,从堆顶取出n-1个节点直至堆中只剩最后一个节点,堆排序即全部结束。
///堆排序
void HeapSort(SeqList R,int n)
{
int i;
for(i=n/2;i>=1;--i)
Sift(R,i,n); //构建大根堆
for(i=n;i>=2;--i)
{
R[0]=R[i];
R[i]=R[1];
R[1]=R[0];
Sift(R,1,i-1);
}
}
完全二叉树的高度h与节点数n的关系为h=[log2n]+1。构建堆时,要进行n/2次筛选运算,每次最多有h-1次比较互换;利用堆排序则要进行n-1次筛选运算,每次也是最多有h-1次比较和互换。因此,整个堆排序过程的时间复杂度是n与h的乘积数量级,O(nlog2n)。