程序猿必学之堆排序

本文介绍了堆排序的基本概念,包括完全二叉树、大根堆和小根堆。通过详细步骤展示了如何将无序数据构建成大根堆,并通过交换堆顶数据与末节点实现排序。堆排序的时间复杂度为O(nlogn),空间复杂度为O(1),是一种不稳定的排序算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前情了解:

要想了解堆排序的过程,首先得知道什么是堆?
要想了解什么是堆,首先得知道什么是二叉树?
而在堆排序中使用的完全二叉树只是二叉树的一种,所以先得了解什么是完全二叉树?

完全二叉树(百度百科):
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
简单来说,就是一棵从上往下,从左往右的必须依次布满节点的树就是完全二叉树。

例如:
在这里插入图片描述
了解了完全二叉树之后,就可以来看一下什么是堆?
堆实际上就是一个完全二叉树,不同的地方在于:它是有规律的完全二叉树,也就是说堆一定是完全二叉树,但完全二叉树却不一定是堆。

对于一棵完全二叉树来说,如果它的任意一个父节点的值都大于或等于它的两个子节点,这样的堆叫做大根堆,又称最大堆(大顶堆);
相反如果一棵完全二叉树,它的任意一个父节点的值都小于或等于它的两个子节点,这样的堆叫做小根堆,又称最小堆(小顶堆)。

如下图示:
在这里插入图片描述
了解完以上概念之后,就可以正式进行堆排序了,拿到一组无序的数据后,先把这组数据整理成一个大根堆或小根堆,至于选择哪一个,取决于你的排序是升序还是降序,一般来说升序建大根堆,降序建小根堆

主题思路:

堆排序主要分为两步,以大根堆为例进行讲解:

  1. 将初始堆也就是一个完全二叉树构建成大根堆
  2. 将堆顶数据与末节点进行交换得到最大的数据
  3. 重复上述步骤

注:在整个排序过程中,堆必须始终保证是大根堆,为森么嘞?
只有堆始终是大根堆,才能保证每次拿到的数据是最大的,并且在调整的过程中,当前子树中的最大数据位于根节点,便于下一次拿到最大的数据

具体过程:

拿到一组数据{ 1,3,4,8,9,2 }之后:

Step1:将初始堆构建成大根堆

a.无序序列的结构如下:
在这里插入图片描述
b.大根堆调整的过程如下:
我们先从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点size/2-1=2,也就是上图的4结点),从左至右,从下至上进行调整。

  • 在以4节点为根节点的子树中,符合大根堆规律,不需要调整;
  • 在以3节点为根节点的子树中,{3, 9, 8}三个节点中,节点9最大,此时交换节点3与节点9,如下图示:
    在这里插入图片描述
    交换完毕后,在以9节点为根节点的子树中,符合大根堆规律,所以不再进行调整;
  • 在以1为根节点的子树中,{1, 4 , 9}三个节点中,节点9最大,此时交换节点1与节点9,如下图示:
    在这里插入图片描述
    交换完毕后,在以9节点为根节点的子树中,符合大根堆规律,所以不再进行调整;
  • 在以1为根节点的子树中,{1, 8 , 3}三个节点中,节点8最大,此时交换节点1与节点8,如下图示:
    在这里插入图片描述
    交换完毕后,在以8节点为根节点的子树中,符合大根堆规律,所以不再进行调整;

此时,整个二叉树符合大根堆的特点,构建大根堆至此结束。

Step2:将堆顶数据与末节点进行交换得到最大的数据
  • 首先将节点9与末节点交换
    在这里插入图片描述
    交换完毕后,对当前二叉树进行调整
    在这里插入图片描述
  • 再将节点8与末节点交换
    在这里插入图片描述
    交换完毕后,对当前二叉树进行调整
    在这里插入图片描述
  • 再将节点4与末节点交换

在这里插入图片描述
交换完毕后,对当前二叉树进行调整
在这里插入图片描述
重复上面的过程,直至循环结束,就会得到一组有序的数据
在这里插入图片描述

稳定性、时间复杂度与空间复杂度:

时间复杂度:
主要在初始化堆过程和每次选取最大数后重新建堆的过程,初始化建堆过程时间:O(n),更改堆元素后重建堆时间:O(nlogn)
综上所述:堆排序的时间复杂度为:O(nlogn)

空间复杂度:
因为堆排序是就地排序,空间复杂度为常数:O(1)

稳定性:不稳定
在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, … 1这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。

代码如下(含详细注释):
void AdjustDown(int array[],int size,int root)
{
	int left = 2 * root + 1;
	int right = 2 * root + 2;
	//左孩子越界
	if (left >= size)
	{
		return;
	}
	//假设左孩子最大
	int max = left;
	//存在右孩子,右>左
	if (right < size && array[right] > array[left])
	{
		//选出子节点中大的那个作为max
		max = right;	
	}
	//如果大于,表示符合大根堆的条件,不需要再进行调整,直接返回
	if (array[root] >= array[max])
	{
		return;
	}
	//将子节点中较大的那个和根节点进行交换
	Swap(array + root, array + max);
	//判断这次交换是否对其余子树有影响,有影响则进行调整,递归执行
	AdjustDown(array, size, max);
}

void CreateHeap(int array[], int size)
{
	//最后一个非叶子节点(最后一个结点的双亲节点)->0
	for (int i = size / 2 - 1; i >= 0; i--)
	{
		AdjustDown(array, size, i);
	}
}

void HeapSort(int array[], int size)//升序建大堆
{                                                               
	CreateHeap(array, size);
	for (int i = 0; i < size; i++)
	{
		//堆中最大的数据位于堆顶,将其与末节点进行交换
		Swap(&array[0], &array[size - i - 1]);
		//判断 减去交换完最大节点之后 堆是否为大堆,不是则进行调整,
		//只有让二叉树始终保证是大堆,才能在每次交换时拿到最大的数据放置在末尾
		AdjustDown(array, size - 1 - i, 0);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值