【数据结构与算法】之堆及其实现!

news2024/11/18 15:49:06

目录

1、堆的概念及结构

2、堆的实现 

2.1 堆向下和向上调整算法

2.2 堆的创建

2.3 建堆时间复杂度

2.4 堆的插入

2.5 堆的删除

2.6 完整代码

3、完结散花


                                                                                个人主页:秋风起,再归来~

                                                                                            数据结构与算法                             

                                                                       个人格言:悟已往之不谏,知来者犹可追

                                                                                        克心守己,律己则安!

1、堆的概念及结构

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

堆的性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

2、堆的实现 

2.1 堆向下和向上调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整 成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

代码实现:

1、向上调整算法:

//交换
void swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}


//向下调整
void AdjustDown(HPDataType* 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;
		}
	}
}

2、向下调整算法

//向下调整
void AdjustDown(HPDataType* 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;
		}
	}
}

2.2 堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算 法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的 子树开始调整,一直调整到根节点的树,就可以调整成堆。

int a[] = {1,5,3,8,7,6}; 

//建堆算法一(从第一个孩子向上调整)
//这种写法的时间复杂度为n*logn
//for (int i = 1; i < len; i++)
//{
//	AdjustUp(a, i);//从第一个孩子向上调整建堆
//}
// 
//建堆算法二(从倒数第一个根向下调整)
//这种写法的时间复杂度为O(N)[更优]
for (int i = (len - 1 - 1) / 2; i >= 0; i--)
{
	AdjustDown(a, len, i);//从倒数第一个根向下调整建堆
}

2.3 建堆时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的 就是近似值,多几个节点不影响最终结果):

因此:向下调整建堆的时间复杂度为O(N)。 

2.4 堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	//先判断容量大小是否足够
	if (hp->_size == hp->_capacity)
	{
		int newcapacity = hp->_capacity == 0 ? hp->_capacity = 4 : hp->_capacity * 2;
		//如果原来没有空间,就给上4,有的话就扩大为原来的两倍
		HPDataType* ptr = (HPDataType*)realloc(hp->_a, newcapacity * sizeof(HPDataType));//动态扩容
		if (ptr == NULL)
		{
			perror("realloc fail;");
			return;
		}
		//也可以用assert断言一下
		hp->_a = ptr;//开辟成功将地址传给arr
		hp->_capacity = newcapacity;//更新容量
	}
	//对堆进行插入操作
	hp->_a[hp->_size] = x;
	hp->_size++;
	//插入一个数据后就进行向上调整,保证堆的结构
	AdjustUp( hp->_a,  hp->_size-1);
}

2.5 堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调 整算法。

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->_size > 0);
	//先交换根与叶子的位置,保证根的左右都是堆,方便后面的根先下调整
	swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	//再删除最后一个数据,即是堆的最小或最大值
	hp->_size--;
	AdjustDown(hp->_a, hp->_size, 0);
}

2.6 完整代码

Heap.h

#pragma once

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

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;
//堆的初始化
void HeapInit(Heap* hp);

// 堆的销毁
void HeapDestory(Heap* hp);

// 堆的插入
void HeapPush(Heap* hp, HPDataType x);

// 堆的删除
void HeapPop(Heap* hp);

// 取堆顶的数据
HPDataType HeapTop(Heap* hp);

// 堆的数据个数
int HeapSize(Heap* hp);

// 堆的判空
bool HeapEmpty(Heap* hp);

Heap.c 

#include"Heap.h"

//堆的初始化
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}

// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->_a);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 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(child>0)
	{
		if (a[child] < a[parent])//< 建小堆;> 建大堆
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	//先判断容量大小是否足够
	if (hp->_size == hp->_capacity)
	{
		int newcapacity = hp->_capacity == 0 ? hp->_capacity = 4 : hp->_capacity * 2;
		//如果原来没有空间,就给上4,有的话就扩大为原来的两倍
		HPDataType* ptr = (HPDataType*)realloc(hp->_a, newcapacity * sizeof(HPDataType));//动态扩容
		if (ptr == NULL)
		{
			perror("realloc fail;");
			return;
		}
		//也可以用assert断言一下
		hp->_a = ptr;//开辟成功将地址传给arr
		hp->_capacity = newcapacity;//更新容量
	}
	//对堆进行插入操作
	hp->_a[hp->_size] = x;
	hp->_size++;
	//插入一个数据后就进行向上调整,保证堆的结构
	AdjustUp( hp->_a,  hp->_size-1);
}

//向下调整
void AdjustDown(HPDataType* 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(Heap* hp)
{
	assert(hp);
	assert(hp->_size > 0);
	//先交换根与叶子的位置,保证根的左右都是堆,方便后面的根先下调整
	swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	//再删除最后一个数据,即是堆的最小或最大值
	hp->_size--;
	AdjustDown(hp->_a, hp->_size, 0);
}

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(hp->_size > 0);
	return hp->_a[0];
}

// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->_size;
}

// 堆的判空
bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}

3、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​​​

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

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

相关文章

OpenCV与AI深度学习 | 高效开源的OCR工具:Surya-OCR介绍与使用

本文来源公众号“OpenCV与AI深度学习”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;高效开源的OCR工具&#xff1a;Surya-OCR介绍与使用 1 背景 在众多企业应用中&#xff0c;光学字符识别 (OCR) 是一项基础技术。在本文中&…

AI视频教程下载:全面掌握ChatGPT和LangChain开发AI应用(附源代码)

这是一门深入的课程&#xff0c;涉及ChatGPT、LangChain和Python。打造专注于现实世界AI集成的AI应用&#xff0c;课件附有每一节涉及到的源代码。 **你将学到什么&#xff1a;** - 将ChatGPT集成到LangChain的生产风格应用中 - 使用LangChain组件构建复杂的文本生成管道 - …

OpenFeign微服务调用组件使用

前言&#xff1a;OpenFeign是可以跨服务、跨进程的调用方式。 什么是Feign Feign是Netflix开发的声明式、模版化的HTTP客户端。 优势: Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验&#xff0c;开发者完全感知不到这是远程方法&#xff0c;更感知不到这…

分布式限流总结

1、计数器 java内部可以使用原子计数器AtomicInteger\Semaphore信号量来做简单的限流 // 限流的个数private int maxCount 10;// 指定的时间内private long interval 60;// 原子类计数器private AtomicInteger atomicInteger new AtomicInteger(0);// 起始时间private lon…

Linux x86_64 UEFI 启动

文章目录 前言一、UEFI二、Disk device compatibility2.1 GPT 磁盘分区表2.1.1 简介2.1.2 Linux 2.2 ESP&#xff08;EFI&#xff09; 文件系统2.2.1 简介2.2.2 LinuxLinux Kernel EFI Boot Stub 三、UEFI GPT grub23.1 简介3.2 引导方式 3.3 BOOTX64.EFI3.4 shimx64.efi3.5 …

pip换源ubuntu

到THU网站上有给定的教程 https://mirrors.tuna.tsinghua.edu.cn/help/pypi/ 方法1 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package然后在https://pypi.org/project/nvidia-cublas-cu12/#files 里面搜索你的包名 方法2 python -m pip install --upg…

caffe在ARM鲲鹏920-openEuler2309上的环境搭建

caffe 配置环境 caffe cpu-only openblas protobuf 编译caffe需要3.6~3.10版本&#xff0c;否则会报错 dnf install只能安装3.19版本 需要从源码编译&#xff0c;这里选择了3.9版本 protobuf的github仓 从源码编译安装 caffe-gpu mode caffe的gpu模式需要用到cuda make…

【机器学习-08】 | Scikit-Learn工具包进阶指南:Scikit-Learn工具包之决策树算法实战分析

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

[猫头虎分享21天微信小程序基础入门教程] 第12天:小程序的自定义组件开发

[猫头虎分享21天微信小程序基础入门教程] 第12天&#xff1a;小程序的自定义组件开发 第12天&#xff1a;小程序的自定义组件开发 &#x1f6e0;️ 自我介绍 大家好&#xff0c;我是猫头虎&#xff0c;一名全栈软件工程师。今天我们继续微信小程序的学习&#xff0c;重点了…

两数交换,数组查找奇数个数的数(位运算)

文章目录 一、异或运算&#xff1a;1.1 Demo1.2 面试题 一、异或运算&#xff1a; 1.1 Demo 0和N进行异或运算都等于N 任何一个数和自己异或运算都等于0 且异或运算满足交换率 a^b b^a eg&#xff1a; a 甲 &#xff0c; b 已 那么则有 a a^b ​ b a^b ​ a a^b 故有&am…

MySQL进阶 日志结尾以及8.0新特性

日志结尾 前面我们聊了mysql的undo日志,redo日志,binlog等等,也从一条update语句来分析了一下日志的执行思路以及版本控制是怎么回事,四大特性是怎么实现的等等 今天我们来说说最后一个错误日志 其实用处不大 因为对我们开发人员来说基本上是没有权限来查看错误日志的 一般…

爬虫工程师---如何用charles把scrapy的爬虫监控起来调试

前言: 想法是,通过charles挂上魔法工具,然后跑代码的时候,实时监控每一个访问的请求状态和返回是个啥? 原理其实和挂手机等一样的;但是,我想说的,让scrapy挂上charles... 之前写过关于fiddler挂代理的路子,还有关于fiddler和charles的证书的文章,不明白的可以去查看 手机模拟器…

Unity Miscellaneous入门

概述 在Unity中有非常多好用的组件&#xff0c;也是Unity为我们提供的方便的开发工具&#xff0c;它的功能可能不是主流的内容&#xff0c;比如渲染&#xff0c;音乐&#xff0c;视频等等&#xff0c;所有Unity把这些内容统一归到了一个杂项文件组中。 Unity组件入门篇总目录-…

Centos 7 上安装【Docker】

安装Docker&#xff08;Centos 7&#xff09; 1.1.卸载&#xff08;可选&#xff09; 如果之前安装过旧版本的Docker&#xff0c;可以使用下面命令卸载&#xff1a; ***注意&#xff1a;***若提示[您需要 root 权限 执行此命令]需要在命令前加上sudo yum remove docker \do…

2. C++服务器编程-信号

什么是信号 其实信号就是一个中断。就是在执行程序的时候突然来了一个信号&#xff0c;然后我们去执行这个新来的程序了&#xff0c;这就是中断。 处理方法 信号的处理方式∶忽略、捕获、默认处理 linux中都有那些信号 man7 signal 比如说kill -9 安装man中文手册 自己百…

数据挖掘实战-基于内容协同过滤算法的电影推荐系统

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

[JAVASE] 类和对象(五) -- 抽象类和接口

目录 一. 抽象类 1.1 抽象类的定义 1.2 抽象类的实现 1.3 抽象类的作用 1.4 抽象类注意事项 二. 接口 2.1 接口的定义 2.2 接口的实现 2.3 接口的作用 2.4 接口注意事项 三. 总结 一. 抽象类 1.1 抽象类的定义 如果一个类中没有包含足够的信息来描绘一个具体的对象, 那么…

Apache无法确定服务器的完全合格域名(FQDN)报错解决方法

文章目录 一、apache语法检查报错二、报错原因分析三、报错解决办法 一、apache语法检查报错 当我们在进行apache配置时&#xff0c;需要检查配置是否存在语法错误&#xff1a; sudo apache2ctl configtestAH00558: apache2: Could not reliably determine the servers fully…

图解CPU、内存、硬盘的工作原理

1、简要概括 硬件作用cpu 负责执行计算机程序的指令内存用于临时存储程序和数据。硬盘将数据从硬盘读取到内存或将数据从内存写入硬盘的过程。联系三者协同工作&#xff0c;CPU执行程序指令&#xff0c;将需要的数据从硬盘读取到内存&#xff0c;进行计算后再将结果写回内存或…

JDBC访问数据库

JDBC学习笔记代码jar包等连接 链接&#xff1a;https://pan.baidu.com/s/1NqgMucUk7JjoSZXEkA-PPQ?pwd60kv 提取码&#xff1a;60kv –来自百度网盘超级会员V3的分享 下载驱动 不管是连接mysql还是sqlserver都需要Java的一个驱动包&#xff0c;及XXX.jar包&#xff0c;这个包…