【数据结构】堆、堆排序(包你学会的)

这篇具有很好参考价值的文章主要介绍了【数据结构】堆、堆排序(包你学会的)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

堆(Heap)

1、堆的概念及结构

果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

2、堆的分类

大顶堆:每个节点的值都大于或者等于它的左右子节点的值。
小顶堆:每个节点的值都小于或者等于它的左右子节点的值。

2.1、小堆的结构

由下图我们可以看出,这是个小堆,因为每一个父亲结点都比其子结点小,例如:父亲12小于子结点17和58,父亲结点17又小于它的子结点27和32,58也小于72,所以它是个小堆
而树形图右侧则是它在数组里面的存储结构。
【数据结构】堆、堆排序(包你学会的),数据结构

2.2、大堆的结构

理解了小堆,大堆同理,只不过和小堆相反,由下图所示 此时的每一个父亲结点都大于其子结点,
【数据结构】堆、堆排序(包你学会的),数据结构

2.3、找到规律并证明

既然堆的逻辑结构是完全二叉树,那么它就同样具有完全二叉树的性质。

对于完全二叉树,若从上至下、从左至右编号,以根节点为0开始,则编号为i的节点,其左孩子结点编号为2i+1,右孩子节点编号为2i+2,父亲节点为 (i-1) / 2。

我们以小堆为例证明一下!

【数据结构】堆、堆排序(包你学会的),数据结构

3、堆的实现(小堆)

3.1、堆的结构以及接口

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>

typedef int HPDataType;
typedef struct Heap
{
    HPDataType* a;
    int size;
    int capacity;
}HP;
//初始化
void HeapInit(HP* php);
//销毁
void HeapDestory(HP* php);
//交换父子结点
void Swap(HPDataType* p1, HPDataType* p2);
//插入
void HeapPush(HP*php,HPDataType x);
//删除
void HeapPop(HP* php);
//堆顶
HPDataType HeapTop(HP* php);
//有效元素个数
size_t HeapSize(HP* php);
//判空
bool HeapEmpty(HP* php);

3.2、初始化、销毁

初始化和销毁我相信各位大佬都没问题,这个我就不再解释了哈

//初始化
void HeapInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}
//销毁
void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

3.3、交换父子结点(后续需要)

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

3.4、插入

插入操作就是先将元素插到堆的末尾,即最后一个孩子的后面,插入之后如果堆的性质发生破坏,将新插入结点顺着其双亲网上调整到合适位置即可。
如下图所示:10就是我们新插入的元素,插入时它在最后一个元素的后面,这时候10为孩子,我们可以根据孩子的下标通过公式parent = (child - 1) / 2可以找到父亲的下标,父亲元素为28,已知我们要建小堆,即孩子>父亲,而现在堆的性质已经受到破坏了,所以我们就要进行“向上调整”。过程如下图:
【数据结构】堆、堆排序(包你学会的),数据结构
代码:

//向上调整
void AdjustUp(HPDataType* a, int child)
{
	//根据孩子找到父亲结点
	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	{
		//因为是小堆,所以父亲比孩子大就交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
			//child = (child - 1) / 2;
			//parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// O(logN)
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		
		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

3.5、删除

这里可不敢和顺序表链表搞杂了,堆的删除就是删除堆顶元素
步骤:

  • 首先把第一个数据和最后一个数据进行交换
  • 减减size
  • 向下调整

过程如下图所示:
【数据结构】堆、堆排序(包你学会的),数据结构
代码:

//AdjustDown就是向下调整
void AdjustDown(int* a, int size, int parent)
{
	//根据父亲找到孩子(左孩子)
	int child = parent * 2 + 1;

	while (child < size)
	{
		// 假设左孩子小,如果解设错了,更新一下
		if (child+1 < size && 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 HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size, 0);
}

3.6、堆顶

这个实现起来就很简单了,直接返回堆顶就好啦,记得断言一下哦~
代码:

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

3.7、获取堆的有效元素个数

也是一样简单,直接返回size就可以啦,也没什么讲的,断言~
直接上代码:

size_t HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

3.8、判空

这个就和上一节的栈的判空一模一样啦,断言然后直接若size为0就返回
直接上代码:

bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

总代码

Heap.h源文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>

typedef int HPDataType;
typedef struct Heap
{
    HPDataType* a;
    int size;
    int capacity;
}HP;

void HeapInit(HP* php);
void HeapDestory(HP* php);
void Swap(HPDataType* p1, HPDataType* p2);
void HeapPush(HP*php,HPDataType x); 
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
size_t HeapSize(HP* php);
bool HeapEmpty(HP* php);

Heap.c源文件
#include"Heap.h"

// 小堆
void HeapInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
			//child = (child - 1) / 2;
			//parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// O(logN)
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		
		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

void AdjustDown(int* a, int size, int parent)
{
	int child = parent * 2 + 1;

	while (child < size)
	{
		// 假设左孩子小,如果解设错了,更新一下

		if (child+1 < size && 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 HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size, 0);
}

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

size_t HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}
Test.c源文件(头文件)
#include"Heap.h"

int main()
{	int a[] = {4,6,2,1,5,8,2,9};
    int n=sizeof(a) / sizeof(int);
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < n;i++)
	{
		HeapPush(&hp, a[i]);
	}
//堆顶前k个元素
// 	int k = 3;
// 	while (k--)
// 	{
// 		printf("%d\n", HeapTop(&hp));
// 		HeapPop(&hp);
// 	}
// }

	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");

	return 0;
}

堆排序(HeapSort)——升序

步骤:

  • 先将待排序的数组构成大堆,这个时候堆顶的元素就是整个堆里最大的元素
  • 然后将堆顶元素和末尾元素进行交换,现在最大元素就是末尾那个结点了,调整排序的时候不把它算进去,此时待排序元素个数为n-1。
  • 把其他的待排元素构成大堆,再回到第一步,交换首尾元素,然后待排元素个数再-1…如此循环

注意:

  • 升序建大堆
  • 降序建小堆

步骤一:向下调整算法建大堆
思路图:
【数据结构】堆、堆排序(包你学会的),数据结构
步骤二:排序
思路图:
【数据结构】堆、堆排序(包你学会的),数据结构
结论:

已知建堆的时间复杂度为O(N),而在排序过程中,由于要每次选出剩余数中最小的数,并保存到每次最后的节点,并要再执行一次向下调整算法,总共需要进行N-1(≈N)次,则向下调整算法的时间复杂度:O(NlogN),再加上建堆的时间复杂度,则=N*logN+N,综上,堆排序的时间复杂度:O(NlogN)

代码:

//升序
void HeapSort(int* a, int n)
{
	//建大堆
	// for(int i=1; i<n; i++)
	// {
	// 	AdjustUp(a,i);
	// }   
	for (int i = (n-1-1)/2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
	//n是数组最后一个元素下标的下一个
	int end=n-1;
	while(end>0)
	{
		Swap(&a[0],&a[end]);
		//需要注意的是 每次交换完之后关系就全乱了 所以需要再次向下调整
		AdjustDown(a,end,0);
		--end;
	}
}

int main()
{
	int a[] = { 4, 6, 2, 1, 5, 8, 2, 9 };

	HeapSort(a, sizeof(a)/sizeof(int));

	for (int i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

测试结果:
【数据结构】堆、堆排序(包你学会的),数据结构文章来源地址https://www.toymoban.com/news/detail-846414.html

到了这里,关于【数据结构】堆、堆排序(包你学会的)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【算法与数据结构】6 学会对算法进行性能测试

    欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享,与更多的人进行学习交流 本文收录于算法与数据结构体系专栏, 本专栏 对于0基础者极为友好,欢迎与我一起完成算法与数据结构的从0到1的跨越 👉传送门:1 详解线性查找法 👉传送门:2 线性查找的优化 👉传送门

    2024年02月04日
    浏览(39)
  • 【数据结构】一篇文章带你彻底学会《后缀表达式》

    创作不易,本篇文章如果帮助到了你,还请点赞 关注支持一下♡𖥦)!! 主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 🔥c语言系列专栏:c语言之路重点知识整合 🔥 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ 后缀表

    2024年02月05日
    浏览(55)
  • 【数据结构】括号匹配问题你学会了吗?来刷刷题检验一下吧!!!

    大家好,很高兴又和大家见面啦!!! 在前面的内容中,我们详细介绍了栈在括号问题中的应用,相信大家看完后对括号问题的解题思路有了更加清晰的认识了。俗话说的好,磨刀不误砍柴工。在今天的内容中,我们就来通过几道习题来加深栈在括号问题中应用吧。 首先我

    2024年04月22日
    浏览(36)
  • 一、课程设计目的与任务《数据结构》课程设计是为训练学生的数据组织能力和提高程序设计能力而设置的增强实践能力的课程。目的:学习数据结构课程,旨在使学生学会分析研究数据对象的特性,学会数据的组织方法,以

    一、课程设计目的与任务 《数据结构》课程设计是为训练学生的数据组织能力和提高程序设计能力而设置的增强实践能力的课程。目的:学习数据结构课程,旨在使学生学会分析研究数据对象的特性,学会数据的组织方法,以便选择合适的数据的逻辑结构和存储结构以及相应

    2024年02月21日
    浏览(66)
  • 【数据结构 — 排序 — 选择排序】

    每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。 2.1算法讲解 • 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素 • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最

    2024年02月05日
    浏览(35)
  • 【数据结构 — 排序 — 交换排序】

    基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。 2.1.算法讲解 以下只讲解冒泡排序代码的简单实现 ,想要更详细的了解冒泡排序

    2024年02月04日
    浏览(43)
  • 【数据结构 — 排序 — 插入排序】

    1.1.1排序的概念 排序: 所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性: 假定在待排序的记录序列中,存在多个具有相同的的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且

    2024年02月05日
    浏览(38)
  • 数据结构—排序—选择排序

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一、选择排序 1、基本思想 2、直接选择排序 3、选择排序的代码实现 二、堆排序 2.1算法讲解 2.2堆排序的代码实现 总结 世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力

    2024年02月01日
    浏览(32)
  • 小白也能学会的链表 | C++ | 算法与数据结构 (新手村)

    本质:找到两个有序数据段中的第一个相同数据 解题:利用set的不重复性,首先把headA都塞到set中,再遍历headB找有没有已经出现在set中的节点即可。 注意! set的数据是ListNode* 不是 int。如果是int可能出现节点不同但是var相同的情况,而ListNode* 就不会。 时间复杂度:O(2n)

    2024年02月14日
    浏览(52)
  • 数据结构——排序算法——归并排序

    在第二个列表向第一个列表逐个插入的过程中,由于第二个列表已经有序,所以后续插入的元素一定不会在前面插入的元素之前。在逐个插入的过程中,每次插入时,只需要从上次插入的位置开始,继续向后寻找插入位置即可。这样一来,我们最多只需要将两个有序数组遍历

    2024年02月09日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包