堆(建堆算法,堆排序)

目录

一.什么是堆?

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【docker】仓库harbor的部署

harbor介绍 Harbor 是一个用于存储和管理 Docker 镜像的开源仓库。它提供了一系列的功能&#xff0c;比如用户管理、访问控制、镜像管理、日志审计和安全扫描等。Harbor 可以作为私有仓库来使用&#xff0c;也可以与公有仓库&#xff08;如 Docker Hub&#xff09;集成使用。 …

03.tomcat环境搭建

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

华为OD机试 - 寻找最富裕的小家庭(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

Python 开心消消乐

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

M00238-固定翼无人机集群飞行仿真平台MATLAB完整代码含效果

一个小型无人机集群仿真演示平台&#xff0c;使用matlab和simulink搭建。 给出的例子是5架的&#xff0c;当然如果你愿意花时间&#xff0c;也可以把它扩展到10架&#xff0c;20架甚至更多。 输入&#xff1a;5架飞机的规划路径 输出&#xff1a;每架无人机每个时刻的13个状态量…

【模拟面试问答】深入解析力扣164题:最大间距(桶排序与排序方法详解)

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

【C++】从零开始构建红黑树

送给大家一句话&#xff1a; 日子没劲&#xff0c;就过得特别慢&#xff0c;但凡有那么一点劲&#xff0c;就哗哗的跟瀑布似的拦不住。 – 巫哲 《撒野》 &#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b; ⛰️⛰️…

指针(6)

1. sizeof和strlen的对比 1.1 sizeof 在学习操作符的时候&#xff0c;我们学习了 sizeof &#xff0c; sizeof 计算变量所占内存内存空间大小的&#xff0c;单位是字节&#xff0c;如果操作数是类型的话&#xff0c;计算的是使⽤类型创建的变量所占内存空间的大小。 sizeof 只…

leetcode:计数质数

class Solution { public:// 如果 x 是质数&#xff0c;那么大于 x 的 x 的倍数 2x,3x… 一定不是质数int countPrimes(int n) {vector<int> isPrime(n, 1);int ans 0;for (int i 2; i < n; i) {if (isPrime[i]) {ans 1;if ((long long)i * i < n) {for (int j …

Linux内核重置root密码

Ubuntu 首先重新启动Ubuntu系统&#xff0c;然后快速按下shift键&#xff0c;以调出grub启动菜单在这里我们选择第二个&#xff08;Ubuntu高级选项&#xff09;&#xff0c;选中后按下Enter键 选择最高的Linux内核版本所对应的recovery mode模式&#xff0c;按e键编辑启动项 在…

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…

数据结构——链表——模板类实现双向链表——先完成再完美——持续更

链表&#xff1a;概念&#xff0c;实现&#xff0c;《数据结构》这里实现是基于模板的 C语言基础&#xff0c;指针&#xff0c;引用。模板。《CPrimer》有些进阶用法放在语言学习的目录 LeetCode应用&#xff0c;会更新在LeetCode150&#xff0c;目前这个系列先暂停&#xff0c…

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

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

云界洞见——基于移动云云数据库MySQL应用实践

目录 简介1 新手入门1.1 创建MySQL实例1.2 公网连接MySQL实例 2 操作指南2.1 创建数据库2.2 数据备份设置2.3 日志管理2.4 监控告警2.5 代码审计 3 应用场景4 总结 如今&#xff0c;大型企业如金融企业和银行等&#xff0c;在下一代的微服务架构转型要求下&#xff0c;需要基础…

C++ prime 第五版 第14章 重载运算与类型转换

一、基本概念 重载的运算符是具有特殊名字的函数&#xff1a;它们的名字由关键字operator和其后要定义的运算符号共同组成。和其他函数一样&#xff0c;重载的运算符也包含返回类型、参数列表以及函数体。 我们不能为内置类型的运算对象重定义运算符。对于一个运算符函数来说&…

【Week-R1】RNN实现心脏病预测,基于tensorflow框架

文章目录 一、什么是RNN&#xff1f;二、准备环境和数据2.1 导入数据 三、构建模型四、训练和预测五、其他&#xff08;1&#xff09;sklearn模块导入报错&#xff1a;ModuleNotFoundError: No module named sklearn&#xff08;2&#xff09;优化器改为SGD&#xff0c;accurac…

MySQL--备份恢复

目录 一、备份恢复的工作职责 1.备份的时间周期 2.备份的方式 3.恢复方案 4.检查备份 5.定期恢复演练 6.故障恢复策略 7.迁移升级 二、逻辑备份工具--mysqldump 1.介绍 2.使用场景 3.mysqldump命令的参数介绍 1&#xff09;全备&#xff1a; 2&#xff09;单库或…

四轮麦轮平衡车四个轮子安放位置要求,以及编码器测速注意事项(强调,否则无法正常平移)——基于STM32F103ZET6

轮子推荐ABBA&#xff0c;当然BAAB也可以 如图安放&#xff1a; 这两种安防位置可以实现平移效果 若要实现平移则需要先实现PID控制平衡&#xff0c;这里用到520编码电机&#xff0c;相较于370电机他的动力更足&#xff0c;在调节PID时能节约不少时间而且更加容易。 需要注意…

基于离散小波变换(DWT)的心电信号伪影去除及心电信号PQRST波检测(MATLAB R2018)

心电信号是心脏神经&#xff0c;肌肉组织电化学活动的表现形式。这些电化学活动使心脏内部产生一系列非常协调的电刺激脉冲&#xff0c;分别使心房&#xff0c;心室的肌肉细胞兴奋&#xff0c;从而有节律的舒张和收缩。这些生物电活动在体表的不同部位形成不同的电位差变化&…

622.设计循环队列

typedef struct {int* a;int head;int tail;int k; } MyCircularQueue;bool myCircularQueueIsEmpty(MyCircularQueue* obj); bool myCircularQueueIsFull(MyCircularQueue* obj);//初始化 MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* obj(MyCircularQue…