(十)1.3_堆排序

在这里插入图片描述

一.相关概念

①什么是堆?
  堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值总是小于等于(或者大于等于)它的父节点。
  1.父结点的键值总是不小于它的两个子结点的键值的堆称之为"大顶堆"(简称"大堆")
  2.父结点的键值总是不大于它的两个子结点的键值的堆称之为"小顶堆"(简称"小堆")

②建什么堆?
  首先,你要确定经过堆排序后获得的有序序列是递增还是递减?
1.如果你要获得排序后的序列是递增,则需要把无序序列建为大堆
2.如果你要获得排序后的序列是递减,则需要把无序序列建为小堆

③怎么建堆?
1.建小堆

  a.先将无序序列按顺序以层序建一棵完全二叉树
  b.从最后一个非叶子结点开始从后往前调整堆
  假如父结点的左右孩子结点均比父节点更大,则不必调整该结点,开始调整前一个结点
  假如父结点的左右孩子结点中更小的结点比父结点更小,则与父结点交换位置,原父结点交换位置后,需继续检查当前位置是否也满足是小堆,如果满足则不必再调整,反之继续调整
  c.就这样从后往前依次调整堆,直到调整到堆顶,一个小堆便建成

2.建大堆

  建大堆与建小堆原理是一样的
  a.先将无序序列按顺序以层序建一棵完全二叉树
  b.从最后一个非叶子结点开始从后往前调整堆
  假如父结点的左右孩子结点均比父结点更小,则不必调整该结点,开始调整前一个结点
  假如父结点的左右孩子结点中更大的结点比父结点更大,则与父结点交换位置,父结点交换位置后,需继续检查当前位置是否也满足是小堆,如果满足则不必再调整,反之继续调整
  c.就这样从后往前依次调整堆,直到调整到堆顶,一个大堆便建成

   注意: 上述说的建完全二叉树并不是开辟新内存以二叉链形式存储二叉树,而是在原顺序表上,以顺序表的形式存储二叉树,并不需要开辟新的内存


通常我们排序后都想得到递增序列,所以我的算法也是建的大堆,建大堆图解如下:

在这里插入图片描述

④如何用堆排序?
   上面我们初始建堆完成,接下来我们就要利用这个初始建好的堆实现排序,这里以大堆讲解(小堆也是同样的原理)
   1.我们把堆顶元素和堆尾元素(也就是顺序表中首元素和尾元素)互换位置,然后把堆尾元素去除(也就是顺序表最后一个元素不会再进行调整)
   2.执行1操作后,堆的元素减少了一个,而且不是大堆了,这个时候我们就要把堆从前往后重新调整为大堆
   3.循环执行1,2操作,直至堆中只有一个元素,这时顺序表中的元素便是有序递增的

过程如图:
在这里插入图片描述


二.思路分析

   1.要以代码实现算法,首先我们要知道这几个知识点:
   假如完全二叉树是以顺序表作为存储结构(0单元不用,从索引1开始存储),那么如果一个父结点在顺序表中的位置序号为s,则其左孩子的序号为2s,右孩子的序号为2s+1;最后一个非叶子结点的序号为结点总数除以2取整([L.length/2])

   2.有了上面这几个知识点我们就可以操作了,首先是初始建堆从最后一个非叶子结点开始(i=L.length/2),从后往前(i–)调整堆,直至调整到堆顶(堆顶结点也要完成调整,所以调整完成后i==0)

  3.调整时,因为要调整的结点未必只需调整一下,可能位置需要多次调整才能确定,所以我们用辅助单元(零单元)暂存要调整的结点元素,到了最后的安放位置才赋值过去,这样可以减少元素移动次数,提高算法效率

  4.然后用堆排序时,每次交换堆顶和堆尾,并去除一个堆尾,就把结点数量减一(i–),然后重新从前往后调整为大堆,也是和3同理,直至堆中只有一个元素(i ==1),顺序表中的元素便是有序递增



三.代码实现

注意:顺序表中零单元不用,从索引1开始存储元素

//堆排序(排列为递增序列) 
void HeapSort(SqList &L)
{
	int i,j,s,m;                //i为建大堆的起点堆顶位置,j为孩子节点位置,s为正在进行建大堆的堆顶位置,m为堆尾位置 
	for(i=L.length/2;i>0;i--)   //初始建大堆,从最后一个非叶子节点开始,从下至上为起点堆顶
	{
		s=i,m=L.length;
		L.elem[0]=L.elem[s];          //辅助单元暂存父结点元素
		for(j=2*s;j<=m;j*=2)
		{
			if(j<m&&L.elem[j]<L.elem[j+1])   //如果左右孩子均存在,并且右孩子大于左孩子 
				j++;             //使j为右孩子位置序号,使j为元素更大的那个孩子的序号 
			if(L.elem[0]>=L.elem[j])      //如果父结点比左右孩子结点都大,不必做任何操作 
				break;   
			L.elem[s]=L.elem[j];         //大元素往上升 
			s=j;			             //继续往下检查是否满足大堆 
		} 
		L.elem[s]=L.elem[0];	         //此时s为父结点元素的安放位置 		
	}	     
	for(i=L.length;i>1;i--)      //输出大堆堆顶的元素(放在堆尾),堆尾元素放在堆顶,然后重新调整为大堆 
	{
		L.elem[0]=L.elem[i];    //辅助单元暂存堆尾元素 
		L.elem[i]=L.elem[1];    //堆顶和堆底元素交换
		L.elem[1]=L.elem[0];
		s=1,m=i-1;              //重新调整大堆 
		L.elem[0]=L.elem[s];    //辅助单元暂存父结点元素 
		for(j=2*s;j<=m;j*=2)
		{
			if(j<m&&L.elem[j]<L.elem[j+1])   //如果左右孩子均存在,并且右孩子大于左孩子 
				j++;                         //使j为右孩子位置序号 
			if(L.elem[0]>=L.elem[j])      //如果父结点比左右孩子结点都大,不必做任何操作 
				break;   
			L.elem[s]=L.elem[j];         //大元素往上升 
			s=j;			             //继续往下检查是否满足大堆 
		} 
		L.elem[s]=L.elem[0];	         //此时s为父节点元素的安放位置					
	}	
}

操作结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值