堆的概念、代码实现与应用

MATLAB之把多个小cell拼成一个数组 问题描述 现有一个cell向量 L (90000,1),每个元素都是由一个(100X9)的double数组组成的.如图. 其中, 每个cell元素都是一个单独的数据, 我们要把它转换成(90000, 19, 100)的三维数组. cell2mat, 但是所有的数据会堆叠起来 L2=cell2mat(L); % (90000, 19) reshape, 变成三维数组 reshape的原理是从上到下, 从左往右拆开重组. 目前只有19这一维数据与要求一致, 那就以这个为突破口. L2=L2'; % 阅读详情

一、树的一些基本概念

节点的度:一个节点含有的子树的个数称为该节点的度;

叶节点或终端节点:度为0的节点称为叶节点;

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;

子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;

如上图:B是A的孩子节点兄弟节点:具有相同父节点的节点互称为兄弟节点;

树的度:一棵树中,最大的节点的度称为树的度;

树的高度或深度:树中节点的最大层次;

森林:由m(m>0)棵互不相交的树的集合称为森林。

树是递归定义的。任何一棵树都包含根和n(n>=0)棵子树两个部分。

子树互不相交(如果相交就是另一种数据结构(图))

除了根节点以外,每个节点有且仅有一个父节点。

1.1 树的节点设计

如何设计树的节点可以表示出树的数据结构呢?

这样设计:

struct TreeNode
{
	int val;

	struct TreeNode* leftchild;
	struct TreeNode* rightBrother;
};

每个节点中有两个指针,一个指向这个节点的第一个(最左边的)子节点,另一个指向这个节点的右边的一个亲兄弟节点。这样从根节点出发就可以找到整棵树的所有节点,非常巧妙。

1.2 二叉树的概念

二叉树是指每个节点最多有两个子节点的树。

满二叉树指的是每个层的节点数都达到最大值的二叉树。h层的满二叉树共有(2^h-1)个节点。

如果一棵满二叉树有N个节点,那么它的层数是log2(N+1)层。

完全二叉树指的是从上到下、从左到右依次填满节点,中间没有空位的二叉树。也就是说,完全二叉树除了最后一层以外的所有层都是满的,并且最后一层的节点是从左到右连续的(不能有空的位置)。

完全二叉树在内存中是连续存储的。这样存储比较抽象,但是它的好处是可以通过下标来计算父子关系:假设父节点在数组中的下标是i,那么左孩子在数组中的下标是2*i+1,右孩子在数组中的下标是2*i+2。如果孩子节点在数组中的下标是j,那么父节点在数组中的下标就是(j-1)/2。

然而,如果不是完全二叉树,就不适合用数组来存储了。

二、堆

2.1 堆的概念

计算机中有两组堆和栈的概念,注意辨析:堆和栈可以是内存中两个不同的区域划分,也可以是两种数据结构。

堆是完全二叉树。分为两种。大堆中每个父节点都大于子节点。小堆中每个父节点都小于子父节点。

2.2 代码实现

2.2.1 初始化与销毁

void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

void HPDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

2.2.2 插入数据

插入数据需要把数据按照顺序放在堆的最后,然后向上调整。以大堆为例,如果当前这个数据比它的父节点的数据大,就让两者交换位置,直到它不再比父节点大。所以,我们需要写一个函数用于向上调整;然后还需要一个用于交换数组中两数据位置的辅助函数。

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 (child > 0)
	{
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HPPush(HP* php, HPDataType x)
{
	assert(php);

	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		php->capacity = newcapacity;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc failed:");
			return;
		}
		php->a = tmp;
	}

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

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

2.2.3 删除数据

删除数据是指删除根节点上那个数据,因为如果是删最后一个数据,只需要size--,没什么写的必要。删除根节点数据,我们先让它与堆里的最后一个数据互换位置,再size--,再把根节点处的数据向下调整到正确的位置。因为每个父节点有两个子节点,所以需要比较一下两个子节点的大小再决定和哪个交换。

AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;

	while(child < n)//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 HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

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

2.2.4 取顶与判空

HPDataType HPTop(HP* php)
{
	assert(php && php->size);

	return php->a[0];
}

bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

三、堆排序

我们可以利用堆来进行效率很高的排序。

比如有一个数组。如果要找到前k个最大的数据,我们只需把它们一个一个插入到一个大堆里,然后取根节点数据(此时根节点的数据就是最大的那个),然后删除它,然后再取根节点数据(此时根节点数据就是第二大的数据)......取k次,就得到前k个最大的数据了。每次取出后按顺序放到数组里,就完成了数组的排序。

void TestHeap1()
{
	int a[] = { 4,2,8,1,5,6,9,7,3,23,444,65,80,3533,66,245};
	HP hp;
	HPInit(&hp);
	for (size_t i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		HPPush(&hp, a[i]);
	}

	//找出最大的前k个:
	int k = 0;
	scanf("%d", &k);

	int i = 0;
	while (k--)
	{
		printf("%d ", HPTop(&hp));
		a[i++] = HPTop(&hp);//数组排序
		HPPop(&hp);
	}
	printf("\n");

	HeapDestroy(&hp);
}

那如果要在数组内部完成排序,而不是先插入堆中再复制出来,该怎么办呢?此时就需要建堆,然后借鉴在堆中删除数据的思路排序,只不过这次不是删除,而是“尾删除”——把数据放到数组的最后面。

void HeapSort(int* a, int n)
{
	//建堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

	int end = n - 1;
	while (end>0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

void TestHeap2()
{
	int a[] = { 4,2,8,1,5,6,9,7,3 };
	HeapSort(a, sizeof(a)/sizeof(int));

}

int main()
{
	TestHeap2();
	return 0;
}

这种排序算法的效率是很高的,远高于之前讲过的冒泡排序(冒泡排序没有实用意义,只有教学意义)。

刚才我们用向上调整的方法建堆,但其实我们还可以用向下调整的方法来建堆。 

//向下建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
	AdjustDown(a, n, i);
}

容易推得向下调整建堆的时间复杂度是O(N),向上调整建堆的时间复杂度是O(N+logN)。

向下调整建堆的效率更优,因为向上调整建堆是节点数多的层调整次数更多,而向下调整建堆是节点数少的层调整次数更多。

四、Top-k问题

刚才讲过的找最大的k个数据的方法,效率很高,时间复杂度是O(N*logN),但是问题在于要建堆必须在内存上开出一个数组,如果数据很多,内存就不够用。

更好的方法是这样的:

用前k个数据建一个小堆,让后面的数据依次与堆顶数据比较,如果比堆顶的数据大,就覆盖掉堆顶的数据,然后向下调整。这样,最后小堆中剩下的k个数据,就是最大的k个。

我们可以造一些数据文件来试一试这个算法。

void CreateNData()
{
	int n = 100000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n; ++i)
	{
		int x = (rand()+i) % 10000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

数据造好了。我们来实现一下这个算法。

void TestHeap3()
{
	int k;
	printf("请输入k:");
	scanf("%d", &k);
	int* kminheap = malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc failed");
		return;
	}
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < k; i++)
	{
		fscanf(fout,"%d", &kminheap[i]);
	}
	//建一个k个数的小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	//读取剩下的N-K个数
	int x = 0;
	while (fscanf(fout,"%d", &x) > 0)
	{
		if (x > kminheap[0])
		{
			kminheap[0] = x;
			AdjustDown(kminheap, k, 0);
		}
	}

	printf("最大的前%d个数是:", k);
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
}

我们已经通过%10000000将造出的数据控制在了10000000以内,接下来我们就可以手动修改数据来看看算法好不好用。

成功挑出了手动修改过的10个数据。说明算法没有问题。

堆的知识点到这里就差不多结束了。

END

《电磁兼容设计》专栏说明 本专栏自2024年1月起开始更新,仅设此一个电磁兼容相关知识专栏,并将一直持续更新下去,无期限 阅读详情

相关推荐

嵌入式常用通信协议 #概述

嵌入式通信协议

AFK3060的博客 2113

堆的实现+堆的应用(堆排序和Topk)

又是一万字的堆啊,你喜不喜欢

adcxhw的博客 1405

关于token无痛刷新的两个方案

目录方案一方案二 方案一 思路: 后端需要返回一个token过期的时间,在请求发起前拦截每个请求,判断token的有效时间是否已经过期,如果已过期,则暂时将请求挂起,先刷新token后再继续请求。 前期思考: 返回过期时间是为了判断是否过期的,但是如果服务器时间和系统时间不一致呢? 如果同时发起多个请求时怎么办? 实现: 因为是在请求前进行拦截,所以这里用axios的axios.interceptors.request.use()方法 首先,关于服务器时间和系统时间不一致的问题,我的解决方法是:

Yugoup的博客 2712

堆的实现详解

堆的实现以及top-k问题

weixin_70238476的博客 2718

DDR内存Prefetch机制详解:从DDR1到DDR4的技术演进与性能提升

本文深入解析DDR内存Prefetch机制从DDR1到DDR4的技术演进与性能提升。通过分析Prefetch技术的实现原理与Burst传输的协同机制,揭示DDR1的2n Prefetch、DDR2的4n Prefetch、DDR3的8n Prefetch以及DDR4的Bank Group架构创新如何显著提升内存带宽与系统性能。

weixin_30369041的博客 535

Java本地AI推理初体验:Jlama+LangChain4j构建离线问答系统

Jlama 是专为 Java 生态设计的现代化大型语言模型(LLM)推理引擎,旨在让开发者无需依赖外部服务即可直接在 Java 应用中运行本地化模型推理。本文将结合Jlama + langchain4j 实现一个本地可运行的简单问答Demo。

2501_92589102的博客 407

【数据结构】--- 博主拍了拍你并向你扔了一“堆”二叉树(堆的概念+结构+代码实现)

数据结构学习第十三弹 --- 二叉树---堆的概念+结构+代码实现

小沈.的博客 2144

Ubuntu22.04 apt 安装 ROS1 ROS Noetic Ninjemys

ubuntu 22.04 apt-get 安装 ros1 ros Noetic 最新版

autolabor的博客 5万+

数据结构初阶-堆的代码实现

堆这个数据结构实现起来比较难,日后需要不断去强化这方面的知识。喜欢的可以一键三连哦,下节讲解:二叉树的链式实现。

博客是在C++/Java/Liunx/算法的学习阶段所写的博客,内容可能不是很全面 1219

Docker一键部署NextCloud+OnlyOffice:打造企业级私有云办公环境(含HTTPS配置)

本文详细介绍了如何使用Docker Compose一键部署NextCloud与OnlyOffice,构建安全、高效的企业级私有云办公平台。内容涵盖从架构设计、Docker编排配置、HTTPS证书自动化管理,到系统初始化、性能优化及高级功能扩展的全栈实战指南,帮助企业实现数据自主可控与高效的在线文档编辑协作。

Apple的专栏 1238

【原理图PCB专题】Cadence 17.4版本导出excel版本坐标文件

Cadence 17.4版本导出坐标文件往往都是txt格式,并且无标题、导出时间、导出设计工程多名称等信息。那么怎么样才能导出有带这些信息的excel版本并格式好看的坐标文件呢?

一点硬件 2763

堆的概念及基本操作实现

1.堆的基本概念: 严格来讲,堆有不同的种类,但是我们在算法学习中,主要用的还是二叉堆,而二叉堆有最大堆和最小堆之分。 最大(最小)堆是一棵每一个节点的键值都不小于(大于)其孩子(如果存在)的键值的树。大顶堆是一棵完全二叉树,同时也是一棵最大树。小顶堆是一棵完全完全二叉树,同时也是一棵最小树。 需要注意的问题是:堆中的任一子树也还是堆,即大顶堆的子树也都是大顶堆,小顶堆同样。

多反思,多回顾,要坚持。 1万+

芒果YOLO11改进145:Loss损失函数KLD:即插即用 NeurIPS顶会论文,独家首发原创新颖|通过 Kullback-Leibler 散度学习用于目标检测的高精度边界框

旋转检测Loss损失函数KLD:即插即用 NeurIPS顶会论文,独家首发原创新颖|通过 Kullback-Leibler 散度学习用于目标检测的高精度边界框

包括YOLOv5、YOLOv7、YOLOv8等模型改进 560

C语言实现堆

堆的概念 如果有一个关键码集合K={K0,K1,…,Kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个数组中,并满足: Ki &amp;lt;= K2*i + 1且Ki &amp;lt;= K2*i + 2(Ki &amp;lt; K2*i + 1且Ki &amp;gt;= K2*i + 2)i = 0,1,2,…,则称为大堆(或小堆)。 通俗点说,堆就是一个任意节点都小于(或大于)其左右孩子节点的一个二叉树。...

erwuhuoche的博客 1万+

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

DS初阶:二叉树的顺序结构及堆的实现

学会利用堆来实现堆排序和TOP-K问题

weixin_51142926的博客 8521

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

【数据结构与算法】堆的实现

本文详细的介绍堆是什么,堆的结构以及堆是如何实现的,本篇重点在于堆的两个调整算法,掌握它们,你就基本可以理解堆是如何实现的,以及堆的应用:堆排序。本篇带有图文解析及代码以供参考。

Extreme_wei的博客 1137

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

机械转码日记【3】——《数据结构》堆的实现及堆的应用

堆是什么?如何实现堆排序?以及TopK问题?看完这篇博客你就懂了!

李鑫的博客 1391

数据结构——堆

1.容易蒙圈的概念问题 一般我在学习一个新的东西的时候都会在脑子里有一大堆问题!那么到底堆是什么?是一种什么样的数据结构呢?用堆可以来干什么呢? 让我来自问自答哈:堆其实说白了就是一种二叉树这类的数据结构,哪种?堆就是一种基于完全二叉树的连结构(完全二叉树就是除了最底层,其它层都必须填满,最后一层可以从左到右填满);平时生活中,我们有时会说一堆人,一堆某某东西,其实数据结构里的堆也和生活中的类似,...

芒果就是没有盲的博客 8666

【树与二叉树】二叉树顺序结构实现以及堆的概念及结构--详解介绍

今天我们认识并学习了二叉树顺序结构的相关概念,并且对堆的概念及结构也有了一定的了解。还对二叉树顺序存储的实例——堆的各接口功能进行了实现。下一篇博客我们将从堆的时间复杂度详解以及堆的应用—堆排序、TOP - K问题进一步介绍堆。希望我的文章和讲解能对大家的学习提供一些帮助。

Sherry的博客 754

一篇解建堆,堆的实现,堆排序,TopK问题(C语言)《数据结构与算法》

这里作者是建的小堆,如果要建大堆就改作者在代码里面声明的地方,就把三个小于改成大于就可以了哈。源代码里面解决了:建堆(给一个数组建堆),堆的各项操作,堆排序,TopK问题。PS:代码里面有详细的解释哈。main函数里面test1里面是建堆的各项操作的测试及堆排序的测试。 test2里面是给一个数组然后建堆的测试哈。

原来45的博客 1609

DS-堆与堆的应用

对堆和堆的应用的简单介绍以及代码实现。

Emilia_is_angel的博客 118

堆以及堆的相关应用

堆简介:本文中的堆是一种树形数据结构,可以把堆看成一种特殊的完全二叉树,再从二叉树上加上一些限制条件即可以构成堆。即要求父节点元素全部都大于或等于子节点元素,或者小于等于。 这就构成了俩种堆: 1.大根堆:A[PARENT[i]] >= A[i] 2.小根堆:A[PARENT[i]] <= A[i] 堆的常见应用为: 使用堆进行排序,也就是常说的堆排序,时间复杂度为nlgn(这是比较排序时间复杂度

LemonTree829的博客 259

【数据结构】堆与堆排序

前言本篇博客我们来仔细说一下二叉树顺序存储的堆的结构,我们来看看堆到底如何实现,以及所谓的堆排序到底是什么若有问题 评论区见📝。

m0_74091744的博客 1089

堆的构建及应用~C语言简单实现

实现堆,堆的应用,TOP-K问题。

qq_58259923的博客 1522

数据结构——lesson7二叉树 堆的介绍与实现

如果有一个关键码的集合K = { k1,k2 ,k3 ,…,kn-1 },把它的所有元素按的顺序存储方式存储在一个中,并满足:ki =k(2i+2) ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。✨✨简单来说大堆指的是父节点都大于子节点的完全二叉树;小堆指的是父节点都小于子节点的完全二叉树;大堆的根节点是最大的,小堆是最小的。

Renswc的博客 2696

【数据结构】C语言实现堆及其应用

int size;} Heap;1.函数AdjustUp和AdjustDown的改变控制者大小根堆的切换。2.大小根堆的切换可以决定堆排序的升序和降序。3.大小根堆的切换以及进堆时比较规则的切换可以决定topK大和topK小。

SHLYYY 1388
上一篇: 队列的概念、C语言实现及应用
下一篇: 二叉树的各种操作
jinyishu_
博客等级 码龄1年 53粉丝 82原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值