堆排序

简单的选择排序中没有把每一次比较的结果保存,在下一次的比较中有许多比较已经在前一次做过了,但由于没有保存,所以下一次的排序又重复执行了这些比较,因而记录的次数很多。

堆排序正是在这个部分有所优化。

堆的基本概念:

是具有一些性质的完全二叉树-----每个结点的值都大于或等于其左右孩子结点的值,称大顶堆;小于等于的,称为小顶堆



若按层序遍历的方式给结点1开始编号,则有:

      Ki>=K2i     Ki>=K2i+1   (小顶堆相反)

     1<=i<=[n/2]  ([n/2]是不超过n/2的最大整数)


这里运用到了二叉树的性质

如果对一棵有n个结点的完全二叉树(深度为[log2n]+1)的结点按层序编号,对任一结点i(1<=i<=n)有:

1.如果 i=1,则结点i是二叉树的根,无双亲;若 i>1,其双亲结点是[i/2]

2.如果 2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i

3.如果 2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1


故对于有n个结点的二叉树,它的i值必须小于等于[n/2]



堆排序算法(Heap Sort)

基本思路:

将待排序的序列构造为一个大顶堆,此时序列最大值即为堆顶的根结点。将它与堆数组的末尾元素交换,此后末尾元素即为最大值。然后将其余的n-1个元素重新构造成一个堆,这样就会得到剩下元素中的最大值,重复步骤,得到有序序列


所用的基本结构:

#define MAX 10

//排序所用的顺序表结构 
typedef struct
{
	int a[MAX+1];  //存储排序数组,a[0]用作临时变量  
	int length;    //记录顺序表的长度 
}Sqlist;

//交换 
void swap(Sqlist *L,int i,int j)
{
	int t=L->a[i];
	L->a[i]=L->a[j];
	L->a[j]=t;	
} 


算法:

//堆排序
void HeapSort(Sqlist *L)
{
	int i;

	for(i=L->length/2;i>0;i--)       //i=L->length/2的意义是根据二叉树的性质推出,它们都是有孩子的结点 
		HeapAdjust(L,i,L->length);
	
	for(i=L->length;i>1;i--)
	{
		swap(L,1,i);
		HeapAdjust(L,1,i-1);	
	}		
} 

解析:

第一个for将待排序的序列构成一个大顶堆

第二个for将每个最大值的根结点与末尾元素交换,并再重新调整成为大顶堆

第6行代码i=L->length/2 即是所有有孩子的结点,以其为参数,调用 HeapAdjust 来进行一步步构建大顶堆


HeapAdjust:

//s标号的结点即为所需调整的根结点  
//该算法主要是找到最大结点,然后将根节点与最大结点交换位置,完成大顶堆的调整 
void HeapAdjust(Sqlist *L,int s,int m)
{
	int t,j;
	t=L->a[s];
	for(j=2*s;j<=m;j*=2)   //沿着关键字较大的孩子结点向下筛选 
	{
		if(j<m&&L->a[j]<L->a[j+1])	//左右孩子比较大小,当右孩子大时,j+1即变为右孩子 
			j++;					
		if(t>=L->a[r])           //当根结点最大时,退出循环 
			break;
		L->a[s]=L->a[j];		//把较大的孩子结点值赋给根结点 ,此时a[s],a[j]都是最大值 
		s=j;					//便于下面的交换值 
	}
	L->a[s]=t;               //交换 
} 

解析:

该算法主要是找到最大结点,然后将根节点与最大结点交换位置,完成大顶堆的调整 

for循环解析:for循环遍历其结点的孩子。

初始条件 j=2*s 意为j是s的左孩子,s的左孩子序号一定是2s右孩子一定是2s+1

以 j*=2 为增量,意味着遍历孩子的孩子

j<m表示不是最后一个结点

第9行代码,当右孩子大于左孩子时,j++转为右孩子

第11行代码,当根结点就是最大结点时,退出循环,直接进行和自己进行交换,序列不变

......


第二个for:

        for(i=L->length;i>1;i--)
	{
		swap(L,1,i);                //将堆顶元素与最后一个序列元素互换 
		HeapAdjust(L,1,i-1);		//重新构成大顶堆,注意i-1 
	}		


复杂度

主要在初始构建堆和重建堆时的反复选择上耗费时间

每个非叶子结点最多进行两次比较和互换,时间复杂度O(n)

第i次取堆顶重建时用时O(logi)  (完全二叉树某结点到根结点距离[log2 i]+1),且取n-1次堆顶,故重建堆复杂度O(nlogn)

综上,时间复杂度O(nlogn)


稳定性

记录的比较和交换是跳跃式的,所以堆排序不稳定

(由于初始建堆比较次数多,故堆排序不适用于待排序序列个数少的情况)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值