DSA之十大排序算法第七种:Heap Sort

堆排序 2019年8月31日13:56:51
之前我们已经分析过 选择排序,我建议:当你在看这篇博客的时候,请先回顾 选择排序:详见DSA之十大排序算法第二种:Straight Selection Sort 原因暂不解释。听话,看一眼花不了多长时间。注:大顶堆和小顶堆原理相同,这里还是提供堆排序的 升序 C++实现,故而选择 大顶堆逻辑结构。

说起堆排序,很容易理解:利用数据结构中 堆的性质与特点所设计的排序算法。堆排序逻辑结构上 是个大顶堆,在物理结构上 是一组地址连续的一维数组。以一种近似于 完全二叉树的结构 满足堆的性质:子结点的键值或索引总是小于(或者大于)它的父节点。

堆是具有以下性质的一棵完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图所示:
在这里插入图片描述
上图所示的大顶堆 映射到一维数组中(或者说 对这棵完全二叉树进行从左到右的层次遍历)即:(数组下标默认从0开始的)在这里插入图片描述

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;src[i] >= src[2i+1] && src[i] >= src[2i+2] 。
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;src[i] <= src[2i+1] && src[i] <= src[2i+2] 。
排序步骤就是:
  1. 将初始待排的无序序列(d0,d1….dn-1)构建成大顶堆 H[0……n-1],此堆为初始的无序区。
  2. 将堆顶元素H[0](当前数列中的最大值)与最后一个元素H[n-1] swap,此时得到新的无序区H[0……n-2]和新的有序区H【n-1】。此刻当然是满足H【n-1】为最大值了。
  3. swap之后,此时新的堆顶H[0]可能违反堆(这个特殊的完全二叉树)的性质,因此需要对当前无序区H[0……n-2]调整为新的堆;接着然后再次将H[0]与本无序区最后一个元素进行swap,从而得到新的无序区H[0……n-3]和新的有序区H【n-2,n-1】。
  4. 继续重复步骤3 直到后面的有序区的元素个数为n-1(就剩下堆顶一个元素了),则此刻整个排序过程就完成了。
    在这里插入图片描述
分析具体实现:

堆排序的过程演示如上图所示。而堆排序一言以蔽之的话,按照我的理解:就是把这个无序的数列 构建成为一个 初始无序但符合性质的大顶堆,此刻的堆顶元素 就是本数列的最大值。此时进行的swap 就是将堆顶元素 和 堆的末尾元素进行交换,——》该最大值 就就位了(处在了他自己该有的位置上)。 下面就只需要进行 其余的n-1个元素的排序即可,根据 减而知之的策略可得,这个算法的有穷性和正确性 不言而喻。但是随着swap 新的堆顶元素很有可能 是不满足堆的性质的,所以 比较复杂的操作就是 重新adjust这个完全二叉树,继续满足这个堆的性质。 然后进行重复 swap——》adjust——》swap 直至有序的数列个数达到n-1,此时也即:数列的后n-1为元素都已经就位了。此时无序序列只剩下一个 堆顶元素了,毫无疑问 它就是最小的那一个。整个排序过程也就可以结束了。

如上图所示(从菜鸟网站“拿”来的):面对下面的一组数据:

12 ,34 ,6 ,21 ,4 ,33 ,45 ,53 ,15 ,5

整个过程 如下图所示:

  1. 构建初始无序 大顶堆,其adjust过程如下:
    在这里插入图片描述
  2. 在大顶堆之上,进行swap操作:将堆顶元素 就位处理。swap之后,继续进行adjust操作和swap操作的循环进行,直到排序成功。
    在这里插入图片描述
分析排序算法:
  1. 算法复杂度:正如上图所示,整个排序过程的关键操作在于 构建堆和每次的adjust操作。无论在哪种情况下:循环建堆的时间复杂度都是O(nlogn)。而堆排序类似于直接选择排序,并不需要额外的空间,其空间复杂度为O(1),即原地工作。或者说:由于每次重新adjust堆的时间复杂度为O(logn),共n- 1次重新adjust堆操作,再加上前面建立堆时n / 2次向下调整,每次调整时间复杂度也为O(logn)。二次操作时间相加还是O(nlogn) 。
  2. 因为堆的结构是节点i的孩子为2 * i+1和2 * i + 2节点,(大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点)。在一个长为n 的数列中,堆排序的过程是从第n / 2-1 (这个是和数组下标相对应的)开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然是不会破坏前后稳定性。但当为n / 2 - 2, n / 2 - 3, … 0这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2-1个父节点交换把后面一个元素交换过去了,而第n / 2 - 2个父节点把后面一个相同的元素没 有交换,那么这2个相同的数据元素之间的稳定性就被破坏了。所以,堆排序是不稳定的排序算法。例如:12,34,21,12,19 共5个数,下标从0,1,2,3,4开始。(这里 我暂且把0号12 3号12 分别称之为先12 后12),在建成树之后的 向下“堆化”操作中:先12会被调整到后12 的后面,相同数据稳定性遭到破坏。
排序算法实现(大根堆):
/**══════════════════════════════════╗
*作    者:songjinzhou                                                 ║
*CSND地址:https://blog.csdn.net/weixin_43949535                       ║
***GitHub:https://github.com/TsinghuaLucky912/My_own_C-_study_and_blog║
*═══════════════════════════════════╣
*创建时间:2019年8月31日16:21:41                                                            
*功能描述:                                                            
*                                                                      
*                                                                      
*═══════════════════════════════════╣
*结束时间: 2019年8月31日23:58:50                                                            
*═══════════════════════════════════╝
//                .-~~~~~~~~~-._       _.-~~~~~~~~~-.
//            __.'              ~.   .~              `.__
//          .'//              西南\./联大               \\`.
//        .'//                     |                     \\`.
//      .'// .-~"""""""~~~~-._     |     _,-~~~~"""""""~-. \\`.
//    .'//.-"                 `-.  |  .-'                 "-.\\`.
//  .'//______.============-..   \ | /   ..-============.______\\`.
//.'______________________________\|/______________________________`.
*/
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;

//下面这是 主要进行调整操作的
void adjust_new_heap(vector<int>& src, int father,int last)
{
	for (int i = father * 2 + 1; i <= last; i=2*i+1)//last是最后可以进行调整到 的元素下标
	{
		if (i < last - 1 && src[i] < src[i + 1])//i是左孩子 且比右孩子小
		{
			++i;
		}
		if (src[i] > src[father])//这里 是处理孩子中 大者和父节点的交换
		{
			swap(src[i], src[father]);
			father = i;
		}
		else break;//都比父节点小  没必要向下 调整了
	}
}

void Heap_sort(vector<int>& src)
{
	int size = src.size();
	//数组的大小是size 则其下标为【0,size-1】

	//第一步:从最后一个父节点开始开始 与其子节点进行比较
	//其下标为 (size-1)/2
	for (int i = (size - 1) / 2; i >= 0; --i)//依次处理这个父节点,进而初始化大顶堆
	{
		adjust_new_heap(src, i,size - 1);
	}
	cout << "数列初次调整:";
	for (int val : src)
		cout << setw(2) << val << " ";
	cout << endl;
	cout << "/*-----------------------------------------------------*/" << endl;
	for (int i = 0; i < size - 1; ++i)
	{
		swap(src[0], src[size - 1 - i]);//这是swap交换值

		cout << "数列此时交换:";
		for (int val : src)
			cout << setw(2) << val << " ";
		cout << endl;

		adjust_new_heap(src, 0, size - 2 - i);

		cout << "数列此时调整:";
		for (int val : src)
			cout << setw(2) << val << " ";
		cout << endl;
	}
}

int main()
{
	int Array[] = { 12 ,34 ,6 ,21 ,4 ,33 ,45 ,53 ,15 ,5 };
	vector<int>myvec(begin(Array), end(Array));

	cout << "数列初始状态:";
	for (int val : myvec)
		cout << setw(2) << val << " ";
	cout << endl;
	cout << "/*-----------------------------------------------------*/" << endl;
	Heap_sort(myvec);
	cout << "/*-----------------------------------------------------*/" << endl;
	cout << "数列最终状态:";
	for (int val : myvec)
		cout << setw(2) << val << " ";
	cout << endl;

	return 0;
}
/**
*备用注释:
*
*
*
*/

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤傲小二~阿沐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值