堆(建堆算法,堆排序)

news2024/12/26 21:49:16

目录

一.什么是堆?

1.堆 

2.堆的储存 

二.堆结构的创建

1.头文件的声明:

2.向上调整

3.向下调整 

4.源码: 

三.建堆算法

1.向上建堆法

2.向下建堆法

四.堆排序

五.在文件中Top出最小的K个数


一.什么是堆?

1.堆 

        堆就是完全二叉树,而且是一种特殊的完全二叉树它需要满足每一个父节点都大于子节点,称为大堆或每一个父节点都小于子节点,称为小堆。而对兄弟节点之间的大小关系并没有要求(为此它并不是有序的)。如下:

2.堆的储存 

         对于完全二叉树有一个更好的储存方法,就是用顺序表来储存,相比链式储存使用顺序表储存的一个很大的好处在于知道一个结点可以很容易的算出它父结点和子结点的下标,还有可以随机访问。

父子结点下标计算公式 :

        左子结点下标 = 父结点下标*2+1

        右子结点下标 = 父结点下标*2+2

        父结点下标 = (子结点下标-1) / 2 

二.堆结构的创建

1.头文件的声明:

Heap.h

#pragma
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define HpDataType int
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* arr;
	int size;
	int cap;
}Heap;
void HeapInit(Heap* php);//堆的初始化
void HeapDestory(Heap* hp);//堆的销毁
void HeapPush(Heap* hp, HPDataType x);//堆的插入
void HeapPop(Heap* hp);//堆的删除
HPDataType HeapTop(Heap* hp);//取堆顶的数据
int HeapSize(Heap* hp);//堆的数据个数
int HeapEmpty(Heap* hp);//堆的判空

void AdjustUP(HpDataType* arr, int child);//向上调整
void AdjustDOWN(HpDataType* arr, int size, int parent);//向下调整
void Swap(HpDataType* a, HpDataType* b);//元素的交换

        其中堆的初始化,堆的销毁,堆的数据个数,堆的判空,和取堆顶数据和顺序表的操作是一样的这里重点来学一下堆的插入,堆的删除。

2.向上调整

        插入元素呢直接往数组最后插入就可以,但是插入后就不一定是堆结构的,所以需要调整。例如一个大堆:

向大堆中插入53

调整后:

代码示例:

void AdjustUP(HpDataType* arr,int child)
{
	int parent = (child - 1) / 2;//计算父节点下标
	while (child>0)//注意这里不能是parent>0
	{
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);//封装一个函数进行交换
			child = parent;//更新子节点
			parent = (child - 1) / 2;//更新父节点
		}
		else
			break;
	}
}

★如果是小堆只需要把if条件的大于号改为小于号

3.向下调整 

        要注意删除元素我们删除的不是尾元素,这样毫无意义,我们删除的是下标为0位置的元素它是整个堆中最小或最大的元素。怎么删除呢?直接将它删除然后后面的元素在覆盖上吗?这样做的话,它就不是堆了,而且元素之间关系将会全部混乱,就需要从0开始创建堆,效率非常低,我们可以把首元素与尾元素互换然后删除尾元素,虽然这个操作过后它也可能就不是堆了,不过我们可以将首元素向下调整,让它成为堆。比刚才的方案效率要高得多。

比如我们删除大堆中的一个元素

调整过程:

调整后的结果:

代码示例:

void AdjustDOWN(HpDataType* arr, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if ((child+1)<size&&arr[child] < arr[child + 1])
			child++;
		if (arr[child] > arr[parent])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}

★如果是小堆只需要把if条件里兄弟节点的大小关系和父子节点的大小关系改变一下就行

4.源码: 

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HeapInit(Heap* ps)//初始化
{
	assert(ps);
	ps->arr = NULL;
	ps->cap = ps->size = 0;
}
void HeapDestory(Heap* hp)//销毁堆
{
	assert(hp);
	free(hp->arr);
	hp->cap = hp->size = 0;
}
void Swap(HpDataType* a, HpDataType* b)//交换元素
{
	HpDataType c = *a;
	*a = *b;
	*b = c;
}
void AdjustUP(HpDataType* arr,int child)//向下调整
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
			break;
	}
}
void AdjustDOWN(HpDataType* arr, int size, int parent)//向上调整
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (arr[child] > arr[child + 1])
			child++;
		if ((child+1)<size&&arr[child] < arr[parent])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
void HeapPush(Heap* ps, HPDataType x)//插入元素
{
	assert(ps);
	if (ps->size == ps->cap)
	{
		int pnc = ps->cap == 0 ? 4 : 2 * ps->cap;
		HpDataType* pnew = realloc(ps->arr, sizeof(HPDataType)*pnc);
		assert(pnew);
		ps->arr = pnew;
		ps->cap = pnc;
	}
	ps->arr[ps->size] = x;
	ps->size++;
	AdjustUP(ps->arr, ps->size - 1);
}
void HeapPop(Heap* hp)//删除元素
{
	assert(hp);
	assert(hp->size);
	if (hp->size == 1)
	{
		hp->size--;
		return;
	}
	Swap(&(hp->arr[0]), &(hp->arr[hp->size - 1]));
	hp->size--;
	AdjustDOWN(hp->arr, hp->size, 0);
}
HPDataType HeapTop(Heap* hp)//取堆顶元素
{
	assert(hp);
	assert(hp->size);
	return hp->arr[0];
}
int HeapSize(Heap* hp)//计算堆元素个数
{
	assert(hp);
	return hp->size;
}
int HeapEmpty(Heap* hp)//判断堆是否为空
{
	assert(hp);
	return hp->size == 0;
}

三.建堆算法

        在学习建堆算法的时候我们以对数组建堆为例,就是把数组的数据之间的关系做成一个堆结构,一般有两种方法,向上调整建堆和向下调整建堆,具体怎么做我们来看下面。

1.向上建堆法

        向上建堆法也就是通过向上调整建堆,我们拿到一个数组后可以把数组的首元素当做堆,第二个元素当做把新的元素插入堆,然后通过向上调整构成新的堆,以此类推下去把数组遍历完后一个堆就建成了。时间复杂度为O(N*logN)

代码示例:

#include<stdio.h>
#include"Heap.h"
int main()
{
	int arr[] = { 1,9,3,7,6,4,2,10,8,5 };
	int size = sizeof(arr) / sizeof(int);
	for (int i = 0; i < size; i++)
		AdjustUP(arr, i);//该函数在上文已给出,这里不再展示
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	return 0;
}

 不过该方法相比向下调整建堆效率比较低,我们来看向下调整建堆法。

2.向下建堆法

        向下建堆法也就是通过向下调整建堆,要注意并不是从首元素开始调整,因为刚开始它并不满足左右子树都是堆结构,所以不能直接从第一个元素开始向下调整。既然要满足左右子树都是堆那么我们可以考虑从最后一个元素开始调整,不过最后一层下面已经没有元素了,它已经是堆,并不用调整,那么我们从倒数第二层开始调整,所以我们先来计算一下倒数第二层最后一个父节点的下标:

                (size-1-1)/2

        第一个size-1得到二叉树的最后一个元素的下标,再减一除以二得到它的父节点的下标。

代码示例:

#include<stdio.h>
#include"Heap.h"
int main()
{
	int arr[] = { 1,9,3,7,6,4,2,10,8,5 };
	int size = sizeof(arr) / sizeof(int);
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
		AdjustDOWN(arr, size,i);//该函数在上文已给出,这里不再展示
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	return 0;
}

它的时间复杂度为O(N)证明如下: 

其中Sn为总的调整次数. 

四.堆排序

        给一个数组建堆后利用堆的性质给数组排序,使其效率更高,这就是一个堆排序。比如现在要对一个数组进行堆排序,第一个问题就是建大堆还是小堆,怎么利用堆来给数组排序。

        要进行升序就需要建大堆,如果建的是小堆,那么堆顶也就是首元素就是最小的元素,并不需要动,那么来处理第二个元素就注意到它并不一定是第二小的元素,只能从第二个元素开始重新建一个小堆,那么每排一个元素都需要重新建一个小堆效率就会变得很低。

        升序建大堆的话,第一个元素就是最大的元素,我们可以让它与最后一个元素互换,然后把堆的元素个数减一(就是把最后一个元素当做是堆外),最后把堆顶元素向下调整,反复操作直到堆的元素个数变为了零。这样一个数组就按升序排好了。

        降序需要建小堆,原理和排升序相同这里就不在赘述。

代码示例:

#include<stdio.h>
#include"Heap.h"
int main()
{
	int arr[] = { 1,9,3,7,6,4,2,10,8,5 };
	int size = sizeof(arr) / sizeof(int);
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
		AdjustDOWN(arr, size,i);
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	while (size)
	{
		Swap(&arr[0], &arr[size - 1]);//交换元素
		size--;
		AdjustDOWN(arr, size, 0);
	}
	printf("\n排序后;\n");
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
		printf("%d ", arr[i]);
	return 0;
}

五.在文件中Top出最小的K个数

        用堆结构的一个好处就在于,不需要排序就能高效的找出最小的前n个元素或最大的前n个元素,现在我们来利用堆来尝试找出文件中最小的K个数,一个比较低效的一个方法就是把文件中涉及到的所以数据都取出来然后把它建成一个小堆,然后Pop出前k次,得到最小的k个数。但是如果这个数据非常的大呢,比如有上亿个数据,那么就会消耗很大的内存空间。

        有一个很优的方法就是只取出文件的前K个数建成一个大堆,也就是说这个堆只用储K个元素,那么堆顶就是这个堆的最大元素,然后继续遍历文件每遍历一个元素都与堆顶元素作比较,如果比堆顶元素小就更新一下堆顶元素(把小的那个变成堆顶元素),然后进行向下调整,直到遍历完整个文件,那么此时堆中的元素就是文件中最小的K个元素。此方法在时间复杂度上与上一方法差不多,但它大大的节省了空间。

代码示例:

#include<stdio.h>
#include"Heap.h"
void CreateNDate()
{
	//造数据,写入文件中
	int n = 10000;
	srand((unsigned int)time(NULL));
	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() % 1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
void PrintTopK(int k)
{
	int* arr = (int*)malloc(sizeof(int) * k);
	assert(arr);
	FILE* fop = fopen("data.txt", "r");
	if (!fop)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < k; i++)//先取出k个建大堆
		fscanf(fop, "%d", &arr[i]);
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
		AdjustDOWN(arr, k, i);
	int x = 0;
	while (fscanf(fop, "%d", &x) != EOF)
	{
		if (arr[0] > x)
		{
			arr[0] = x;
			AdjustDOWN(arr, k, 0);
		}
	}
	for (int i = 0; i < k; i++)//输出堆中元素
		printf("%d ", arr[i]);
}
int main()
{
	CreateNDate();
	int k = 0;
	scanf("%d", &k);
	PrintTopK(k);
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1700367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AIGC产业链上下游解析及常见名词

文章目录 AIGC上游产业链 - 基础层AIGC中游产业链 - 大模型层与工具层AIGC下游产业链 - 应用层AIGC产业链常见的名词表 在上一章节为大家介绍了 “大模型的不足与解决方案” &#xff0c;这一小节呢为大家针对AIGC的相关产业进行一个拆解&#xff0c;以及相关的一些专业名词做出…

RK3568笔记二十六:音频应用

若该文为原创文章&#xff0c;转载请注明原文出处。 一、介绍 音频是我们最常用到的功能&#xff0c;音频也是 linux 和安卓的重点应用场合。 测试使用的是ATK-DLR3568板子&#xff0c;板载外挂RK809 CODEC芯片&#xff0c;RK官方驱动是写好的&#xff0c;不用在自己重新写。…

C语言 | Leetcode C语言题解之第113题路径总和II

题目&#xff1a; 题解&#xff1a; int** ret; int retSize; int* retColSize;int* path; int pathSize;typedef struct {struct TreeNode* key;struct TreeNode* val;UT_hash_handle hh; } hashTable;hashTable* parent;void insertHashTable(struct TreeNode* x, struct Tr…

第八篇【传奇开心果系列】Python微项目技术点案例示例:以微项目开发为案例,深度解读Dearpygui 编写图形化界面桌面程序的优势

传奇开心果博文系列 系列博文目录Python微项目技术点案例示例系列 博文目录前言一、开发图形化界面桌面程序的优势介绍二、跨平台特性示例代码和解析三、高性能特性示例代码和解析四、简单易用特性示例代码和解析五、扩展性强示例代码和解析六、现代化设计示例代码和解析七、知…

【PB案例学习笔记】-09滚动条使用

写在前面 这是PB案例学习笔记系列文章的第8篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gitee…

如何使用KNN

导入文件和库 加载数据集、拆分数据集 训练模型 预测 打印结果

用C#调用SAP 的WebServices接口

文章目录 用C#调用SAP 的WebServices接口创建C#的项目添加窗体添加引用在表单的装载事件里编写代码运行结果SAP的RFC函数 用C#调用SAP 的WebServices接口 创建C#的项目 添加窗体 添加引用 在表单的装载事件里编写代码 using System; using System.Collections.Generic; using …

MicroLED:苹果对知识产权的影响

Yole的洞察揭示&#xff0c;MicroLED IP在经历了七年的爆炸式增长后&#xff0c;已然屹立于行业之巅。苹果公司&#xff0c;作为微LED领域的先行者&#xff0c;早在2014年便敏锐地捕捉到Luxvue这家初创公司的潜力&#xff0c;将其纳入麾下&#xff0c;引发了业界的广泛关注。然…

204页 | MES项目需求案例方案:效率+精细化+品质+数据互联(免费下载)

【1】关注本公众号&#xff0c;转发当前文章到微信朋友圈 【2】私信发送 MES项目需求案例方案 【3】获取本方案PDF下载链接&#xff0c;直接下载即可。 如需下载本方案PPT/WORD原格式&#xff0c;请加入微信扫描以下方案驿站知识星球&#xff0c;获取上万份PPT/WORD解决方案&…

上位机图像处理和嵌入式模块部署(f103 mcu运行freertos)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 mcu一般有两个用途,一个是控制,一个是非控制。控制类的应用一般要求实时性比较高,什么时候做什么,都是有严格的时间要求的。而非控制类的应用,则要求实现尽可能多的功能,比如…

如何调用通义千问大模型API

目录 登录阿里云 大模型服务平台百炼 登录控制台 QWen Long QWen 通义千问开源系列 大语言模型 OpenAI接口兼容 登录阿里云 阿里云-计算&#xff0c;为了无法计算的价值 大模型服务平台百炼 降价信息&#xff1a; 登录控制台 右上角取得API key 创建Key QWen Long qw…

03.tomcat环境搭建

上传软件包 JDK #man bash #PATH 存放命令的路径 ## ls #加入环境变量&#xff0c;注意&#xff1a;EOF的单引号的意思就是追加到文件中的内容带有变量的不做解析&#xff0c;否则会被解析 cat >>/etc/profile <<EOF export JAVA_HOME/application/jdk export PAT…

修改元组元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 场景模拟&#xff1a;伊米咖啡馆&#xff0c;由于麝香猫咖啡需求量较大&#xff0c;库存不足&#xff0c;店长想把它换成拿铁咖啡。 实例08 将麝香猫…

Python 开心消消乐

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

硬盘录像机DVR如何连接到外网的视频监控接入网关(国标网关)

目录 一、要求 二、工作准备 三、接入 1、查看SIP服务器ID号和相关国标对接参数&#xff0c;如下&#xff1a; 2、DVR国标参数配置 3、进行连接 &#xff08;1&#xff09;设备接入 &#xff08;2&#xff09;配置通道 &#xff08;3&#xff09;通道接入 &#xff0…

蓝桥杯算法心得——李白打酒(加强版)

大家好&#xff0c;我是晴天学长&#xff0c;记忆化搜索&#xff0c;找到技巧非常重要&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 2) .算法思路 1.memo三维表示记录的结果 3&#xff09;.算法步骤 1…

[ FreeRTOS 基础知识] RTOS 背景介绍

文章目录 RTOS简介IOTOSFreertos简介RTOS划分 RTOS简介 &#xff08;1&#xff09;实时操作系统&#xff0c;本用于追求实时性的嵌入式系统。 典型&#xff1a;ucos、uclinux、vxworks &#xff08;实时性的指当事件产生的时候&#xff0c;需要花多久的时间做出响应。&#xf…

俄罗斯半导体领域迈出坚实步伐:首台光刻机诞生,目标直指7纳米工艺

近日&#xff0c;国外媒体纷纷报道&#xff0c;俄罗斯在半导体技术领域取得了重要突破&#xff0c;首台光刻机已经制造完成并正在进行严格的测试阶段。这一里程碑式的事件标志着俄罗斯在自主发展半导体技术的道路上迈出了坚实的一步。 据俄罗斯联邦工业和贸易部副部长瓦西里-什…

【电源专题】功率电感器啸叫原因及典型案例

啸叫产生的原因 声波是在空气中传播的弹性波,人的可听到的频率范围大约20~20kHz。在DC-DC转换器的功率电感器中,当流过人耳可听范围频率的交流电流以及脉冲波时,电感器主体会发生振动,该现象称为"线圈噪音",有时也称为啸叫。 啸叫一般是由电感器产生,…

cmake使用交叉编译工具链并验证

目录 一、内容 二、配置 1. 准备cmake文件 2. 使用交叉编译 三、验证 1. 构建阶段验证 2. 编译阶段验证 一、内容 目的&#xff1a;在X86环境下编译ARM平台软件 编写交叉编译配置文件&#xff1a;xx.cmake 执行cmake命令时指定&#xff1a;cmake \ -DCMAKE_TOOLCHAIN_F…