堆排序

(1)堆的概念:对n个关键字序列k1,k2,k3,...,kn,当且仅当满足下述关系是成为堆:

   ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n/2),当然,这是小根堆,大根堆则换成>=号。k(i)相当于二叉树的非叶子结点,K(2i)则是左子节点,k(2i+1)是右子节点。

    若将此序列所存储的向量k[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。

(2)堆排序思想(小根堆)

    对n个待排序的记录,首先根据各记录的关键字按堆的定义排成一个序列(即建立初始堆),从而由堆顶得到最小关键字的记录,如此反复进行出堆和将剩余记录调整为堆的过程,当堆仅剩下一个记录出堆时,则n个记录已按出堆的次序排成有序序列。因此,堆排序的过程分为两步:

    a、建立初始堆

    首先将待排序的n个关键字分别放到一颗完全二叉树的各个节点,此时完全二叉树各个节点不一定具备堆得性质。由二叉树的性质可知,所有序号大于n/2的树叶节点已经是堆(因为叶子节点没有子节点)。故初始建堆事宜序号为n/2的最后一个非终端节点开始堆的构建的,n/2,n/2-1,n/2-2,...,为根节点的子树满足堆的定义,直到序号为1的根节点为止,则n个关键字构成一个堆。在对根节点序号为i的子树建堆过程中,可能要对节点的位置进行调整以满足堆的定义。但是这种调整可能会导致原来的堆的下一次子树不满足堆的定义,这就需要对下一层进行调整,直到树叶节点。

    b、调整成新堆

    堆顶点的关键字输出后,如何将剩下的n-1个节点调整为堆?首先,将堆中序号为n的最后一个节点与待出堆的序号为1的堆顶节点交换,这时只需要是序号从1~n-1的节点满足堆的定义,即可由这1~n-1个节点构成新的堆。相对于原来的堆,此时堆顶节点发生了改变,而其余n-2个节点任然满足堆的定义,我们只需要堆这个新的堆顶节点进行调整就ok了。

       调整方法:将根节点与左、右孩子节点中关键值较小的节点进行交换,若与左孩子交换,则左子树堆被破坏,且仅左子树的根节点不满足堆的定义。若与右孩子交换,则右子树堆被破坏,且仅右子树的根节点不满足堆的定义。对不满足堆定义的子树继续进行上述的交换操作,这种调整需要持续到叶节点或者到某个节点满足堆的定义为止。

(3)堆排序的过程

    堆n个关键字序列先建成堆(构建初始堆),然后执行n-1趟排序。第一趟将序号为1的根节点与序号为n的节点交换(第n个节点用于存储出堆节点),并调整前n-1个节点为新的堆;第二趟将序号为1的根节点与序号为n-1的节点交换(第n-1个节点用于存储出堆节点),并调整前n-2个节点为新的堆...第n-1趟将序号为1的根节点与序号为2的根节点交换。此时,待调整的堆仅为序号为1的根节点故无需进行调整,整个堆排序过程结束。

注意:小根堆,对应降序排列(小的被放到后边,大的放到前边);大根堆,对应升序排列(大的放后边,小的放前边)

<pre name="code" class="cpp">/********************************************************************
*This function to do HeapSort
*Author:xiefanfan
*Time:2015-04-04  10:25
*All Rights Reseved
********************************************************************/
#include "stdafx.h"
#include <stdio.h>

/*******************this function to do heap sort based on large root heap*************************/
/*********************对R[s]~R[t]除R[s]外均满足堆得定义,即只对R[s]进行调整,使R[s]为根的完全二叉树成为一个堆***********************/
void HeapAdjust(int R[],int s,int t)
{
	int i,j;
	int temp = R[s];               //temprory variable
	i=s;
	for (j=2*i;j<=t;j=2*j)         //沿关键字较大的孩子向下调整,先假定为左孩子   
	{
		if (j<t&&R[j]<R[j+1])     //如果左孩子比右孩子小,则沿右孩子向下调整
		{
			j=j+1;
		}
		if (temp>R[j])            //如果temp比左右孩子都大,满足大根堆的定义了,不在向下调整
		{
			break;
		}
		R[i]=R[j];                //将关键字较大的孩子节点和双亲节点交换
		i=j;                      //定位于孩子节点  继续向下调整
	}
	R[i]=temp;                    //找到合适的位置,将temp放置进去
}

//进行堆排序,R[]是一个数组,n是数组的个数
void HeapSort(int R[],int n)
{
	int i;
	for (i=n/2;i>0;i--)              //完全二叉树  非终端节点需要进行堆定义调整,从R[n/2],R[n/2-1],.....R[0]
	{
		HeapAdjust(R,i,n);
	}
	int temp;
	for (i=n;i>0;i--)                //对初始堆进行n-1趟排序
	{
		temp = R[1];
		R[1] = R[i];
		R[i] = temp;
		HeapAdjust(R,1,i-1);         //将未排序的前i-1个节点重新调整为堆
	}	
}

int main()
{
	int shuzu[] = {0,61,33,48,82,72,11,25,48};
	HeapSort(shuzu,8);                 //调用堆排序函数
	int index;
	for(index=1;index<9;++index)
	{
		printf("%d\t",shuzu[index]);
	}
}

    堆排序花费的时间主要在构建初始堆和n-1趟堆排序。最坏情况下的堆排序时间复杂度为O(nlogn),所以最坏情况下的堆排序时间复杂度要低于快速排序。

    由于堆排序初始建堆比较此时较多,因此堆排序不适宜记录较少的情况。对大量记录的排序来说,堆排序非常有效。此外,堆排序只需要一个辅助空间,空间复杂度为O(1)。而且,堆排序是不稳定的方法。

 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值