开开心心学算法:堆排序算法的图文详解 & 代码示例

堆包括两种形式:大顶堆和小顶堆

  1. 大顶堆:父节点大于等于子节点;
  2. 小顶堆:父节点小于等于子节点。

堆排序过程主要由三个步骤组成:

  • 构建完全二叉树
  • 构建初始化堆(大顶堆/小顶堆)
  • 排序

贯穿整个堆排序过程的一个重要算法就是对堆进行调整。下面我们按照上面三个步骤介绍详细的堆排序过程。(这里我们以大顶堆为例展开介绍)

构建完全二叉树

给定一个数组Array=[4, 12, 3, 2, 16, 9, 10, 14, 8, 7]
Step1:构建一个完全二叉树
根据数组创建一个完全二叉树

创建初始化堆(本例为大顶堆)

Step2:初始化大顶堆,从编号为5的节点开始进行调整(由于堆排序是自底向上的排序方式,从编号最大的内节点开始调整)。

调整的思路:如果i号节点比它的两个子节点都大,那么就不进行调整;如i号节点比子节点小,那么选择最大的子节点与i号节点进行交换。

初始化大顶堆

  • 左上图荧光标记部分得到16>7,因此保持原状态不变。然后看右上图
  • 右上图荧光标记部分通过比较[2,14,8],可以得出14最大,那么将14与2交换,得到局部堆
  • 左下图中可以看出[3, 9, 10]中,10最大,将3与10进行交换
  • 直到交换到编号1为止,此时编号1位置的值最大
  • 如下图所示,是第一轮调整过后的模型,但此时还不是大顶堆
    继续调整该二叉树
    由于4与16发生了交换,导致该树的左子树的堆结构被破坏,因此需要进行重新调整。但是我们直到以4为根节点的两个子树在前面已经是堆结构了,那么我们在调整时,直接比较[4, 14, 12]之间哪个最大,选择最大的那个值与4交换,我们发现14最大,因此14与4做交换。实质上,这就是一个自顶向下的调整过程。直到最后的模型成为堆结构为止。最后的堆结构模型如下图所示:
    初始化大顶堆结构模型
    目前,我们已经完成了初始化堆模型的构建。接下来我们将进行堆排序操作。一会我们可以看到,对于堆的排序,实际上也是一个不断调整堆的过程。

堆排序

Step3:堆排序。

基本思想:交换a[1]和a[length],将第一个元素与最后一个元素交换,因为根据我们Step2中的操作,我们已经得到了该数组中的最大值,我们将最大值扔到末尾,然后在对剩下的元素a[1……9]进行堆调整,然后我们就可以得到第二大的元素,再把第二大元素扔到剩下元素的末尾,不断进行该操作,我们最后就可以完成堆排序了。

下面我们将用图作为演示:

  • 第一次交换,交换a[1]和a[10](即交换16和7)
    第一次交换
    交换以后最大的元素到了编号为10的位置,将该节点从堆中摘除,后续排序中不用再考虑它了。可以发现,a[1……9]的堆结构又被破坏了,咱们按照前面的调整方案继续对该堆进行调整。蓝色笔迹部分就是调整后的堆结构。当结构再次编程堆结构时,咱们再重复上面的交换操作,这一次就交换a[1]和a[9],那么第二大的元素就到了编号为9的位置了,将该节点从堆中摘除。继续对破坏了的堆结构进行调整,直到所有的节点都从结构为堆的二叉树上被摘除为止。
  • 第二次交换,交换a[1]和a[9]:
    第二次交换
  • 第三次交换,交换a[1]和a[8]:
    第三次交换
    步骤都是一样的,就不全部展示了,下面放出最后的结果
  • 最后的结果图如下所示:
    最终排序结果

代码分析

创建堆

代码部分我们分为两个部分来介绍,与上面的图形化过程一致。

  1. 创建堆(大顶堆)
    假定我们现在有一颗完全二叉树,该树的左右子树已经构成了大顶堆,但是该树本身并不是一个大顶堆,假如树的结构如下图所示:
    在这里插入图片描述
    为了让上图所示的非大顶堆调整称为一个大顶堆的结构,我们需要进行上面的步骤“创建初始化堆”,那么感性的理解有了,如何将上述过程表现在代码层面呢?

通过画图分析我们可以知道,如果要将上图的非大顶堆转化为大顶堆需要从根开始向下调整,一直调整到整个树结称为一个大顶堆的结构。

  • 第一步:i 指向根节点,i = 1(假设根节点的下标从1开始),j 指向它的左子树,j = i * 2;
  • 第二步:比较 i 的两个子树的大小,如果a[j] < a[j + 1],则让 j 指向大的那个子树,进行 j++;否则 j 保持不变;【在上图中,a[j] > a[j + 1](5>4),故 j 指向值为5的节点】
  • 第三步:判断父节点和 j 所指向的子节点的大小,如果a[i] < a[j],那么就让a[i] = a[j],再继续把 j 当成父节点,2 * j 当成父节点进行遍历。

代码如下所示:

// 传入的参数:
// a : 待调整的数组
// low : 数组中第一个值得下标,也就是根节点的下标
// high : 数组中最后一个值的下标,也就是最后一个叶子节点的下标
void createAHeap (vector<int>& a, const int low, const int high){
   
	int i = low, j = 2 * i;		// i指向根节点,j指向它的左子树
	int tmp = a[i];
	while(j <= high){
   
		// 比较左右子树,让j指向大的那个
		if(j < high && a[j] < a[j+1]) j++;
		// 比较父节点和子节点,子节点大的话就将父节点覆盖掉
		if(tmp < a[j]){
   
			a[i] = a[j];
			i = j;
			j = 2 * i;
		}
		else break;
	}
	a[i] = tmp;
}

使用上面的代码就能完成一次调整。调整之后的结果如下图所示。在这里插入图片描述
但是!,咱们想要的理想是一个完全不成形的完全二叉树被调整为一个大顶堆,只进行一次遍历是肯定不够的!所以,我们要怎么做才能将一个乱七八糟的完全二叉树调整成一个理想的堆结构呢?
答案很简单,一个简单的for循环就ok了。

void createHeap(vector<int>&
  • 20
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值