C语言数据结构与算法---选择排序---堆排序

一. 堆的定义

堆是具有如下性质的完全二叉树
大顶堆:每个结点的值都大于或等于其左右孩子结点的值
小顶堆:每个结点的值都小于或等于其左右孩子结点的值

在这里插入图片描述
判断数组是否是堆:
按照层序遍历的方式给结点从1开始标号。
在这里插入图片描述
堆排序:
若在输出堆顶的最小值(最大值)后,使得剩余 n-1 个元素的序列又建成一个堆,则得到n个元素的次小值(次大值)…如此反复,便能得到一个有序序列,这个过程称为堆排序

二. 堆排序需要解决的问题

  1. 如何在输出堆顶元素后,调整剩余元素为一个新的堆?

以小根堆为例:

  1. 输出堆顶元素后以堆中最后一个元素代之
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 然后将根结点值与之左右子树的根结点值比较,并与其中小者进行交换
    在这里插入图片描述
    在这里插入图片描述
  • 若为大根堆,则与大者交换
  1. 重复上述操作,直至叶子结点,将得到的新的堆,称这个从堆顶至叶子的调整过程为“筛选

三. 算法实现

1. 筛选(堆调整)的算法实现

//已知 L->r[s...m] 中记录的关键字除 L->r[s] 外均满足堆的定义
// 本函数是调整 L->r[s] 关键字成为大顶堆
// m 是堆的大小
void HeapAdjust(Sqlist *L,int s,int m)
{
	int temp,j;
	temp = L->r[s];  //记录需要调整的元素的值
	// j 初始化为 s 的左子树的下标
	// j = j*2 每次循环后,j 都为左子树下标
	for(j = 2*s;j <= m;j = j*2)  //沿关键字较大的结点向下筛选
	{
		if(j < m && L->r[j] < L->r[j+1])  // j+1 是 s 的右子树;若左子树小与右子树,将 j 变为右子树下标
		{
			j++;
		}
		// temp 是需要调整的下标值,j 是传进来的结点的左右子树中值较大的值
		if(temp >= L->r[j])
		{
			break;  //若这个值大于左右子树,直接跳出循环
		}
		L->r[s] = L->r[j];//若之前结点的值小于左右子树,就把左右子树中的较大值给这个结点
		s = j;//此时需要改变的结点是左右子树中较大的结点,再次循环
	}
	L->r[s] = temp;  //插入
}

为什么是 j*=2 递增呢?
因为这是一颗完全二叉树,当前结点序号是 s,其左孩子的序号一定是 2s,右孩子序号一定是 2s+1。

2. 堆的建立

单结点的二叉树是堆

  • 在完全二叉树中所有以叶子结点(序号 i > n/2)为根

例如:建立小根堆
在这里插入图片描述
在这里插入图片描述
从最后一个叶子结点开始,向前调整

  1. 调整从 n/2 个元素开始,将以该元素为根的二叉树调整为堆
    在这里插入图片描述
    在这里插入图片描述
  2. 将以序号为 n/2-1 的结点为根的二叉树调整为堆
    在这里插入图片描述
  3. 再将以序号为 n/2-2 的结点为根的二叉树调整为堆
    发现不需要调整
  4. 再将以序号为 n/2-3 的结点为根的二叉树调整为堆
    在这里插入图片描述
    代码实现:
//将无序序列 L->r[1...n]建成大根堆
void CreatHeap(Sqlist *L)
{
	int i;
	int n = L->length;
	for(i = n/2;i > 0;i--) //从最后一个非叶子结点开始循环
	{
		HeapAdjust(L,i,n);
	}
}

3. 堆排序

// 对顺序表 L 进行堆排序
void HeadSort(Sqlist *L)
{
	int i;
	int n = L->length;
	CreatHeap(L)//将无序序列建成大顶堆
	for(i = n;i > 1;i--) // 进行 n-1 趟排序
	{
		swap(L,1,i);//将最后一个元素与堆顶记录交换
		HeapAdjust(L,1,i-1);//将L->r[1...i-1]重新调整为大顶堆
	}
}

堆排序的前半部分,把顺序表构成了大顶堆结构,后半部分只需要每次把一个堆顶值放到末尾的地方,就完成了排序的过程,相当于是升序排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值