服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++超详细分析优化排序算法之堆排序

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

2023-02-28 14:2136°熨斗的焦虑日记 C/C++

堆是计算机科学中一类特殊的数据结构的统称,通常是一个可以被看做一棵完全二叉树的数组对象。而堆排序是利用堆这种数据结构所设计的一种排序算法。本文将通过图片详细介绍堆排序,需要的可以参考一下

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

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

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

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

1、向下调整算法

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

先从定义开始学习:

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

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

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

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

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

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

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

先从根节点开始处理:

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

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

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

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

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

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

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

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

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

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;
}

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

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

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

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

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

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

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

//数组
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++堆排序内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/leadera_/article/details/128462277

延伸 · 阅读

精彩推荐
  • C/C++C语言入门的一些基本资源推荐和程序语法概览

    C语言入门的一些基本资源推荐和程序语法概览

    这篇文章主要介绍了C语言入门的一些基本资源推荐和程序语法概览,C语言是很多现代高级编程语言的基础,需要的朋友可以参考下...

    C语言教程网4672021-03-17
  • C/C++Visual C++中MFC消息的分类

    Visual C++中MFC消息的分类

    Visual C++中MFC消息的分类,标准(窗口)消息:窗口消息一般与窗口内部运作有关,如创建窗口,绘制窗口,销毁窗口,通常,消息是从系统发到窗口,或从窗...

    C++教程网2182020-11-10
  • C/C++C语言实现二叉搜索树的完整总结

    C语言实现二叉搜索树的完整总结

    这篇文章主要介绍了C语言实现二叉搜索树的完整总结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们...

    奋斗的龙猫3912021-10-16
  • C/C++C语言简明介绍常见关键字的用法

    C语言简明介绍常见关键字的用法

    关键字是C语言非常重要的一部分,熟练的掌握和使用关键字有助于我们更加熟悉了解C语言,同时C语言的关键字也是面试笔试中常考的内容。C语言的关键字...

    要努力丫!8362022-12-22
  • C/C++VC实现批量删除指定文件的方法

    VC实现批量删除指定文件的方法

    这篇文章主要介绍了VC实现批量删除指定文件的方法,是一个比较普遍且实用的功能,需要的朋友可以参考下...

    C语言程序设计7612021-01-24
  • C/C++C++动态数组类的封装实例

    C++动态数组类的封装实例

    这篇文章主要介绍了C++动态数组类的封装,很重要的概念,需要的朋友可以参考下...

    C++教程网11242021-01-28
  • C/C++浅析C++的特殊工具与技术

    浅析C++的特殊工具与技术

    以下是对C++中的特殊工具与技术进行了详细的分析介绍,需要的朋友可以过来参考下...

    C++教程网5262020-12-23
  • C/C++C语言中操作utmp文件的相关函数用法

    C语言中操作utmp文件的相关函数用法

    这篇文章主要介绍了C语言中操作utmp文件的相关函数用法,包括getutent()函数和setutent()函数以及endutent()函数,需要的朋友可以参考下...

    C语言教程网4832021-03-09