重温经典:堆排序

堆排序是利用堆的性质进行排序的过程。堆排序包括构成初始堆和利用堆排序这两个过程,堆分成大根堆和小根堆,这里我们使用大根堆来展开讨论。

例如我们有如下一组数据和其对应的完全二叉树:

{35、10、17、40、39、9、50、44、16、26}

                                            

构成初始堆就是把待排序的元素序列{R0~Rn},按照堆的定义调整成堆{R0’~Rn’},因为是一颗完全二叉树,根据完全二叉树的性质可知:

  1. 节点索引为i的左孩子的索引是 (2*i+1)
  2. 节点索引为i的左孩子的索引是 (2*i+2)
  3. 节点索引为i的父结点的索引是 floor((i-1)/2)

(4)节点索引为i的父结点大于其左右子节点,Ri’ >=R2i+1’  , Ri’ >=R2i+2’

为此需要从对应的完全二叉树中编号最大的非叶子节点(索引为n/2-1)起遍历至树根节点(索引为0),依次对遍历到的每个节点进行筛选,以便每个节点形成堆(即父节点不小于左右两个子节点),当遍历至根节点后,就将整棵树变成了一个初始堆。

下面介绍下如何对每个非叶子节点Ri进行筛选,以便构成以Ri为根的堆。因为当对Ri进行筛选时,比他索引大的节点都以进行过筛选,即已经构成了以各自非叶子节点形成的堆,这其中包括Ri的左右子节点(非叶子节点)R2i+1 , R2i+2为根的堆,所以对Ri进行筛选就是比较其与左右子节点的操作:首先把Ri的值与左右子节点中较大值比较,若Ri的值大于等于子节点的值则Ri节点筛选完毕,若子节点的值较大,则使用这个子节点和Ri交换值,交换值后可能会破坏该子节点的堆,还需要再将其与子节点继续比较形成堆,以此类推直至父节点的值都大于等于子节点或者子节点为空时结束。至此,以Ri为根节点的堆就形成,在对Ri节点调整成堆的过程中,若他小于子节点会被逐层下移,就像是筛子一样,小的被漏下大的被留下,所以把这个过程称为筛运算。

下图是是对待排序元素{35、10、17、40、39、9、50、44、16、26}构成的初始堆的全过程。因为节点数n=10,所以从编号n/2 -1 =4的节点起遍历至根节点,依次对每一个节点进行筛运算。 

 

根据堆运算和上面的堆初始化过程可以知道编号为0

的节点A[0](即堆顶)是堆中最大的元素,所以利用堆排序的过程就比较简单了,首先把A[0]和A[n-1]互换,使得A[n-1]成为最大元素,接着对A[0]~A[n-2]再进行筛运算,又得到A[0]~A[n-2]区间内的最大值,再讲堆顶元素和A[n-2]互换,以此类推,经过n-1次对换和筛选后所有节点都有序,排序完毕。

在上面已经构成的堆上进行前两个元素排序流程如下图:

完整代码:

void Sift(int *A,int n,int i)
{
    int x=A[i];
	int j=2*i+1;
	while(j<=n-1)
	{
	    if(j<=n-1 && A[j]<A[j+1])
		{
		    j++;
		}
		if(x<A[j])
		{
		    A[i]=A[j];
			i=j;
			j=2*i+1;
		}else{
		    break;
		}
	}
	A[i]=x;
}

void HeapSort(int *A,int n)
{
    int x;
	int i;
	for(i=n/2-1;i>=0;i--)
	{
	    Sift(A,n,i);
	}
	for(i=1;i<=n-1;i++)
	{
	    x=A[0];
		A[0]=A[n-1];
		A[n-1]=x;
		Sift(A,n-1,0);
	}
}

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值