C++超详细分析优化排序算法之堆排序

 更新时间:2023年02月09日 08:53:36   作者:36°熨斗的焦虑日记  
堆是计算机科学中一类特殊的数据结构的统称,通常是一个可以被看做一棵完全二叉树的数组对象。而堆排序是利用堆这种数据结构所设计的一种排序算法。本文将通过图片详细介绍堆排序,需要的可以参考一下

堆排序,学习了整整一天才把这个排序彻底搞明白……

首先第一点,堆排序是直接选择排序的一种优化排序算法。由于直接排序算法的遍历次数过多,导致直接排序算法的时间复杂度为O(N^2),不适合排大量数据,堆排序应运而生。

堆排序(Heap Sort)进行的改进是能够保存一部分在每次遍历整个数组找出最大(小)值、次大(小)值,主要利用的就是完全二叉树这种数据结构。(后面说是如何保存这些数据的)

堆排序最重要的知识点无非两个:

1、向下调整算法

2、堆的逻辑结构是一棵完全二叉树

先从定义开始学习:

向下调整算法:顾名思义就是从上到下进行数据的调整,可以将完全二叉树调整为最大堆与最小堆(这两种堆也同时被称为“大顶堆”和“小顶堆”)这种算法的前提是:根节点的左右两棵子树均以建成最大(小)堆。

最大堆:所有的父节点都大于子结点

最小堆:所有的父节点都小于子结点

完全二叉树:从上到下、从左到右依次排列的一种树(即从第一层到第n-1层都是满的,只有第n层不满且从左到右排列数据)

(以建小堆为例)看一种典型的示例:

向下调整算法就是处理这种完全二叉树的一种算法,经过这种算法可将此数组建成最小堆。

先从根节点开始处理:

9 为父(根)节点,0,1都是其子节点,0 < 1;所以将0与9作一次交换;父节点同时下移至子节点,子节点变为新父节点的子节点:(p = parent, c = child)

9 为父(根)节点,2,3都是其子节点,2 < 3;所以将2与9作一次交换;父节点同时下移至子节点,子节点变为新父节点的子节点:

9 为父(根)节点,6 是其子节点,6< 9;所以将6与9作一次交换;父节点同时下移至子节点,子节点变为新父节点的子节点:

发现此时新的子节点已经越界,故停止向下调整;整个堆现已完成建堆成为最小堆!

这便是所谓的“向下调整算法”。

了解了以上知识后,还得知道父节点与子结点的表示方法:

leftchild = parent * 2 + 1;

rightchild = parent * 2 + 2;

parent = (leftchild - 1) /2;

下面代码实战:

//向下调整:
//根节点左右子树必须已经成堆
void AdjustDown(int a[], int n, int parent)
{
	int child = parent * 2 + 1;
	//左孩子不能越界
	while (child < n)
	{
		//如果只有左孩子,那就不用判断两个孩子的大小,直接判断左孩子和父亲的大小
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}
		//向下调整
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

简单的交换函数:

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

堆排序的思想现在已经有了雏形:

将一个数组想象成堆,建堆,然后将堆顶最大(小)值置于堆底作为有序数据,这时会新形成一个堆,比之前的堆少一个数据,并且只有根节点的那棵小树未成堆,左右子树已形成大(小)堆,用一次向下调整算法即可将新堆再次建成最大(小)堆。

现在的问题是我们选择建一个最大堆还是最小堆呢?

我们不妨假设建了最小堆,也即上面我们刚刚构建好的堆:

不难发现这样是将最小值筛选出来,再向下调整,选出次小值,这样一来会得到降序的一个数组,反之,若使用最大堆,会得到一个升序的数组。

我们建大堆来得到一个升序数组,现有此无序数组:

//数组
int a[] = { 5,9,6,1,7,2,0,4,3,8 };
//元素个数
int n = (int)sizeof(a) / sizeof(a[0]);

第一步就是建堆:

我们会发现:这样“不听话”的数组显然不符合向下调整算法的前提条件,所以我们可以从这个数组中找能用这个算法的地方:从后向前去调整,最后一个叶子节点?一个数据,不需要调整;

最后一个父节点?他将会有0-2个子节点,而且只有这三个数据,不管怎么“不听话”,这个最小单位会满足“根的左右子树成堆”的这个条件,下一次再将这个父节点-1,即可实现对前一个父节点进行向下调整,循环此步骤直至真正的根节点,这时整个数组会被建成最大堆。

void HeapSort(int a[], int n)
{
    //建堆
	int parent = (n - 1 - 1) / 2;
	while (parent >= 0)
	{
		AdjustDown(a, n, parent);
		parent--;
	}
}

第二步就是排序:

建成堆后,我们需要进行数据的交换形成有序数据区。

void HeapSort(int a[], int n)
{
    //建堆
	int parent = (n - 1 - 1) / 2;
	while (parent >= 0)
	{
		AdjustDown(a, n, parent);
		parent--;
	}
	//已经成最大堆,不用再从最后一个父节点建堆
	//每次只用改变根节点的堆(根左右堆已为最大堆)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

堆排序完毕!

整个代码分享:

#include <stdio.h>
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//向下调整:
//根节点左右子树必须已经成堆
void AdjustDown(int a[], int n, int parent)
{
	int child = parent * 2 + 1;
	//左孩子不能越界
	while (child < n)
	{
		//如果只有左孩子,那就不用判断两个孩子的大小,直接判断左孩子和父亲的大小
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}
		//向下调整
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int a[], int n)
{
	int parent = (n - 1 - 1) / 2;
	while (parent >= 0)
	{
		AdjustDown(a, n, parent);
		parent--;
	}
	//已经成最大堆,不用再从最后一个父节点建堆
	//每次只用改变根节点的堆(根左右堆已为最大堆)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}
void print(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
int main()
{
	int a[] = { 5,9,6,1,7,2,0,4,3,8 };
	int n = (int)sizeof(a) / sizeof(a[0]);
	HeapSort(a, n);
	print(a, n);
	return 0;
}

到此这篇关于C++超详细分析优化排序算法之堆排序的文章就介绍到这了,更多相关C++堆排序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言开发之归并排序详解及实例

    C语言开发之归并排序详解及实例

    这篇文章主要介绍了 C语言开发之归并排序详解及实例的相关资料,需要的朋友可以参考下
    2017-03-03
  • 基于C语言代码实现扫雷游戏

    基于C语言代码实现扫雷游戏

    这篇文章主要为大家详细介绍了基于C语言代码实现扫雷游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • C语言深入讲解语句与选择结构的使用

    C语言深入讲解语句与选择结构的使用

    这篇文章主要为大家介绍了C语言的语句与选择结构,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • C++输入输出注意事项总结

    C++输入输出注意事项总结

    这篇文章主要介绍了C++输入输出注意事项总结,对C++的输入输出各个注意事项进行了很好的总结,需要的朋友可以参考下
    2014-08-08
  • C语言动态内存的分配实例详解

    C语言动态内存的分配实例详解

    动态内存管理同时还具有一个优点,当程序在具有更多内存的系统上需要处理更多数据时,不需要重写程序,下面这篇文章主要给大家介绍了关于C语言动态内存分配的相关资料,需要的朋友可以参考下
    2022-06-06
  • CRC校验原理及其C语言实现详解

    CRC校验原理及其C语言实现详解

    循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术。本文主要介绍了CRC校验原理及其C语言实现,感兴趣的可以了解一下
    2023-03-03
  • C语言中关于sizeof 和 strlen的区别分析

    C语言中关于sizeof 和 strlen的区别分析

    本文通过示例简单分析了4种情况下C语言中sizeof 和 strlen的区别,算是个人经验的一个小小的总结,如有遗漏还请大家告知。
    2015-02-02
  • C语言模拟实现简单扫雷游戏

    C语言模拟实现简单扫雷游戏

    这篇文章主要为大家详细介绍了C语言模拟实现简单扫雷游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10
  • C语言 数据结构之链表实现代码

    C语言 数据结构之链表实现代码

    这篇文章主要介绍了C语言 数据结构之链表实现代码的相关资料,需要的朋友可以参考下
    2016-10-10
  • C++实现AVL树的完整代码

    C++实现AVL树的完整代码

    AVL树是高度平衡的而二叉树。它的特点是:AVL树中任何节点的两个子树的高度最大差别为1。 今天通过本文给大家分享C++实现AVL树的完整代码,感兴趣的朋友一起看看吧
    2021-06-06

最新评论