两篇文章讲透数据结构之堆(一)!

news2024/11/18 23:44:04

目录

1.堆的概念

2.堆的实现方式

3.堆的功能

4.堆的声明

5.堆的实现

5.1堆的初始化

5.2堆的插入

5.2.1向上调整算法

5.2.2堆的插入 

5.3堆的删除

5.3.1向下调整算法

5.3.2堆的删除

5.4获取堆顶元素

5.5获取堆的元素个数

5.6判断堆是否为空

5.7打印堆

5.8建堆

5.8.1向上调整建堆

5.8.2向下调整建堆

5.9销毁堆


1.堆的概念

堆(Heap)可以被看作一种特殊的数据结构,堆通常是一个可以被看成完全二叉树的数组对象

根据堆的数值的关系,可以将堆分为两类:

  • 若满足任意结点的值>=其子结点的值,则可称为大根堆。
  • 若满足任意结点的值<=其子结点的值,则可称为小根堆。

通俗解释来说:

若是任意一个根结点的值都大于其子结点,则被称为大根堆,因为根大!

若是任意一个根结点的值都小于子结点,则是小根堆,因为根小!

2.堆的实现方式

既然堆是一种二叉树,那么我们就可以采用链式存储或者数组存储来实现这个数据结构。但是考虑到完全二叉树的特性,我们在这里采用数组存储的方式,因为这样既方便访问,也不会浪费空间。

既然使用数组存储,就会用到下标,而我们每次插入和删除,都需要计算父结点和子结点的位置。

现在我们取出大根堆的前五个元素,并总结一下父节点和子结点下标的关系。

我们可以总结出上图红字的规律,现在我们具体推导一下这个规律。

因此,我们可以得到如下结论:

  • 若双亲节点存在,则其下标为(i-1)/2。
  • 若孩子节点存在,左孩子下标为2i+1,右孩子为2i+2。

3.堆的功能

  • 堆的初始化。
  • 堆的插入。
  • 堆的删除。
  • 获取堆顶的元素。
  • 堆的元素个数。
  • 堆的判空。
  • 输出堆。
  • 建堆。
  • 销毁堆

4.堆的声明

因为我们是用数组创建堆的,因此堆的声明和顺序表的声明类似! 

typedef int HpDataType;
typedef struct Heap
{
	HpDataType* a;//存储数据
	int size;//大小
	int capacity;//容量
}Heap

5.堆的实现

5.1堆的初始化

堆的初始化与顺序表的初始化类似。

void HeapInit(Heap* hp)//堆的初始化
{
	assert(hp);
	hp->a = NULL;//初始化的时候也可以不开空间
	hp->capacity = 0;
	hp->size = 0;
    //hp->size=hp->capacity=0;
}

5.2堆的插入

我们插入一个值进入堆,是进入到堆目前最后一个元素的后面吗?

其实并不一定,我现在给出大家这么一个场景。

如果目前有如此一个大堆:

现在我们要往里插入一个8,是插入到下标为5的地方吗?

肯定不是的,那么他应该插入到什么地方呢? 

很显然,他应该取代掉5的位置,那么,新的堆就应该是下图:

 那么我们应该如何达到这个效果呢?

    我们刚刚已经计算了父节点和子节点的关系,而由于堆是一个完全二叉树,我们在这里先插入其左结点再插入右结点。而左节点和右节点之间是没有大小关系的,因此我们插入结点之后需要和其父节点比较大小关系,如果比父结点大的话就交换,小的话就不动即可。

    而如果第一次比较会发生交换,由于我们不确定它与更上一层的父结点的大小关系,因此我们还需要继续进行比较,直到不会发生交换为止。

    于是乎,向上调整算法诞生了!

5.2.1向上调整算法

void AdjustUp(Heap* hp, int child)//向上调整
{
	int parent = (child - 1) / 2;
	while (child > 0)//循环结束条件是孩子成为了根节点
	{
		if (hp->a[child] > hp->a[parent])
		{
			//交换
			swap(&hp->a[child], &hp->a[parent]);//这个函数自己写
			//更新新的父结点和子结点
			child = parent;
			parent = (child - 1) / 2;
		}
		//没有发生交换就跳出
		else
		{
			break;
		}
	}
}

5.2.2堆的插入 

现在我们就可以着手于完成堆的插入了。

void HeapPush(Heap* hp, HpDataType x)//堆的插入
{
	assert(hp);
	//判断是否开空间
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity*2;
		HpDataType* tmp = (HpDataType*)realloc(hp->a, newcapacity * sizeof(HpDataType));
		if (!tmp)
		{
			perror("realloc fail");
			exit(1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	//更新数值  后置++,先使用,再++
	hp->a[hp->size++] = x;
	AdjustUp(hp, hp->size - 1);
}

5.3堆的删除

堆的删除是删除堆顶元素,但是问题又出现了,删除了堆顶元素之后,后面的元素怎么办?顺着继承上去吗?那堆原来的亲属关系就全乱了,属实是父子叔侄变兄弟,闹了个哄堂大孝了。

那么这里我们要怎么处理呢?

我们在这里选择的处理方式是,先将堆顶元素和堆尾元素互换位置,之后直接让数组的长度-1。之后再调整根结点和其子节点之间的关系。调整的方法称为向下调整算法

5.3.1向下调整算法

刚刚已经介绍过了向上调整算法,我们也可以推测出向下调整算法的原理是和父节点和子节点的不断比较,但是这里又出现了一个问题,我们要和左子节点还是右子节点比较?

结论:

  • 大堆,则根结点和子结点中较大的值比较
  • 小堆,则根结点和子结点中较小的值比较

原理:这里以大堆为例,如果我们让根结点和较小的孩子值比较,而且发生了交换的话,那么现在的根结点是小于原来较大的孩子的,有悖于堆的定义。

现在我们实现一下向下调整算法

void AdjustDown(int* a, int n, int parent);
{
	int child = parent * 2 + 1;//假设左孩子更大
	while (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;
		}
	}
}

5.3.2堆的删除

堆的删除的思路刚刚已经说过了,我们现在完成它即可。 

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

5.4获取堆顶元素

获取堆顶元素,首先要确保堆存在而且堆内有元素。

我们返回堆顶元素,直接返回数组中的第一个元素即可。

HpDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);
	return hp->a[0];
}

5.5获取堆的元素个数

获取堆的元素个数,就是返回堆的size,我们直接返回即可。

int HeapSize(Heap* hp)//堆的大小
{
	assert(hp);
	return hp->size;
}

5.6判断堆是否为空

判断堆是否为空,就是判断hp的size成员是否等于0,也是直接返回即可。

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

5.7打印堆

数组的打印谁不会啊?

void HeapDisplay(Heap* hp)//堆的打印
{
	for (int i = 0; i < hp->size; ++i)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

5.8建堆

建堆,就是给一个数组不断的调整,调整成为一个堆。

因此我们要传入的参数为堆、数组、数组长度。

首先,我们要在堆中开辟一块和数组空间等大的空间来存储数组元素。

之后,我们需要将参数中的数组中的元素拷贝到我们创建的空间中。

再之后,我们就可以通过我们的调整算法建堆了。

5.8.1向上调整建堆

向上调整建堆就是采用向上调整算法建堆,在这里我们可以用堆的插入取代上述开辟内存的操作。

void HeapCreatUp(Heap* hp, HpDataType* arr, int n)
{
	assert(hp && arr);
	//将每个元素向上调整即可
	for (int i = 0; i < n; i++)
	{
		HeapPush(hp, arr[i]);
	}
}

5.8.2向下调整建堆

向下调整建堆就是采取向下算法建堆,在这里我们首先要给堆开辟一个内存,然后拷贝内存,最后采用向下调整算法建堆即可。 

void HeapCreatDown(Heap* hp, HpDataType* arr, int n)
{
	assert(hp && arr);
	//给堆开辟内存
	HpDataType* tmp = (HpDataType*)malloc(sizeof(HpDataType) * n);
	if (tmp == NULL)
	{
		perror("malloc fail!");
		exit(-1);
	}
	hp->a = tmp;
	//将arr中的元素拷贝到堆中
	memcpy(hp->a, arr, sizeof(HpDataType) * n);
	hp->size = n;
	hp->capacity = n;
	//         最后一个父节点下标          父节点-1
	for (int i =((n - 1) - 1) / 2; i >= 0; i--)
	{                   
		AdjustDown(hp->a, n, i);
	}
}

5.9销毁堆

与顺序表的销毁方式一样,直接销毁即可。 

void HeapDestroy(Heap* hp)//销毁堆
{
	assert(hp);
	free(hp->a);
	hp->size = hp->capacity = 0;
}

6.堆的头文件

Heap.h

#include<stdio.h>
#include<string.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 AdjustUp(Heap* hp, int child);//向上调整
void HeapPush(Heap* hp, HpDataType x);//堆的插入
bool HeapEmpty(Heap* hp);//判断堆是否为空
size_t HeapSize(Heap* hp);//堆的大小
void AdjustDown(int* a, int n, int parent);//向下调整
void HeapPop(Heap* hp);//删除堆顶元素
HpDataType HeapTop(Heap* hp);//获取堆顶元素
void HeapDisplay(Heap* hp);//堆的打印
void HeapDestroy(Heap* hp);//销毁堆
void HeapCreatUp(Heap* hp, HpDataType* arr, int n);//向上调整建堆
void HeapCreatDown(Heap* hp, HpDataType* arr, int n);//向下调整建堆

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

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

相关文章

【C++初阶】vector

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

全局配置路径无法识别的解决——后端

在全局配置路径reggie.path的时候&#xff0c;无法正常启动SpringBoot项目 Value("${reggie.path}")private String basePath; 查看application.yml的配置情况: 发现path没有起作用&#xff0c;推测是格式问题&#xff0c;冒号后面空格后即可

计算机网络(1

网络初识 目录 网络初识一. 网络分类1. 局域网LAN(Local Area Network):2. 广域网WAN(Wide Area Network): 二. 组建网络的基础设备1. 路由器2. 交换机 三. 标识符 协议 (protocol)一. 协议分层1. 分层的好处2. OSI七层分层3. TCP/IP五层模型(或四层) 模型(1. 物理层(可不算)(2…

全栈式数据统计:SqlAlchemy怎样连接MsSql Server获取视图列表

1.源代码 #-----------获取数据库视图列表----------------------------- # -------密码含特殊字符使用 from urllib.parse import quote_plus as urlquotefrom sqlalchemy import create_engine, MetaData, inspect# 替换为你的数据库连接字符串 DRIVER "ODBC Driver 1…

Gin与OpenAPI(Swagger)的使用

一、背景 1、swagger与openapi Swagger&#xff1a; 一种用于描述RESTFUL API的规范&#xff0c;它提供了一种简单的来描述API的请求和相应参数、错误码、返回数据类型等信息&#xff0c;是开发者可以方便了解API使用方式。 官网: https://swagger.io/ OpenAPI : 始于 …

Excel透视表:快速计算数据分析指标的利器

文章目录 概述1.数据透视表基本操作1.1准备数据&#xff1a;1.2创建透视表&#xff1a;1.3设置透视表字段&#xff1a;1.4多级分类汇总和交叉汇总的差别1.5计算汇总数据&#xff1a;1.6透视表美化&#xff1a;1.7筛选和排序&#xff1a;1.8更新透视表&#xff1a; 2.数据透视-数…

经典单链表OJ题

目录 1、轮转数组 2、返回倒数第k个节点 3、判断链表的回文结构 4、两个链表&#xff0c;找出它们的第一个公共结点 5、随机链表的复制 6、判断链表中是否有环 7、返回链表开始入环的第一个节点 1、轮转数组 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k…

Influence blocking maximization on networks: Models, methods and applications

abstract 由于各种社会和贸易网络的不断出现&#xff0c;网络影响力分析引起了研究者的极大兴趣。基于不同的影响力传播模型&#xff0c;人们提出了许多网络影响力最大化的新模型和方法。作为传统影响力最大化问题的延伸和扩展&#xff0c;影响力封锁最大化问题已成为研究热点&…

Python文件和数据格式化-课后作业[python123题库]

文件和数据格式化-课后作业 一、单项选择题 1、文件句柄f&#xff0c;以下是f.seek(0)作用的是&#xff1a;‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪…

spring cache(三)集成demo

一、demo 1、pom <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…

银河麒麟服务器系统xshell连接之后主动断开,报错socket error event:32 Error:10053问题分析

银河麒麟服务器系统xshell连接之后主动断开&#xff0c;报错socket error event&#xff1a;32 Error&#xff1a;10053问题分析 一 问题描述二 系统环境三 问题分析3.1 与正常机器对比sshd文件内容以及文件权限3.2 检查同网段内是否配置多个相同的IP地址 四 后续建议 一 问题描…

每日一题《leetcode--398.随机数索引》

https://leetcode.cn/problems/random-pick-index/ 根据题目所知&#xff0c;所给的数组中有重复的元素。让我们随机输出给定的目标数字的下标索引。 typedef struct {int *sum;int length; } Solution;Solution* solutionCreate(int* nums, int numsSize) {Solution* obj (So…

数美滑块研究

周一&#xff0c;在清晨的阳光照耀下&#xff0c;逆向山脚下的小镇宁静而安详。居民们忙碌地开始一天的生活&#xff0c;而在爬虫镇子的边缘&#xff0c;一座古朴的道观显得格外神秘。 阿羊正静静地坐在青石长凳上&#xff0c;摸鱼养神。突然&#xff0c;一道清脆的声音在他耳…

【Arthas】阿里的线上jvm监控诊断工具的基本使用

关于对运行中的项目做java监测的需求下&#xff0c;Arthas则是一个很好的解决方案。 我们可以用来 1.监控cpu 现成、内存、堆栈 2.排查cpu飚高 造成原因 3.接口没反应 是否死锁 4.接口慢优化 5.代码未按预期执行 是分支不对 还是没提交&#xff1f; 6.线上低级错误 能不能不重启…

Go语言的构建标签(build tag)有何用途?

文章目录 原因解决方案示例代码总结 Go语言的构建标签&#xff08;Build Tags&#xff09;有何用途&#xff1f; 在Go语言中&#xff0c;构建标签&#xff08;Build Tags&#xff09;是一种特殊的注释&#xff0c;它用于控制Go编译器在构建代码时是否包含某些文件。这些标签可…

网页上的超链接复制到Excel中+提取出网址+如何保存

定义 超链接网页标题地址栏 使用的工具 2024年的WPS是不行的&#xff0c; 如果把知乎网页上的超链接复制到WPS中的Excel中&#xff0c;就会丢掉地址&#xff0c;只剩下网页标题 具体操作&#xff08;转载,在Excel2013上验证可行&#xff09; [1]启用【开发工具】&#xff…

HTTP content-type MIME 类型(IANA 媒体类型)

Content-Type(MediaType)&#xff0c;即是Internet Media Type&#xff0c;互联网媒体类型&#xff0c;也叫做MIME类型。在互联网中有成百上千中不同的数据类型&#xff0c;HTTP在传输数据对象时会为他们打上称为MIME的数据格式标签&#xff0c;用于区分数据类型。最初MIME是用…

【老王最佳实践-6】Spring 如何给静态变量注入值

有些时候&#xff0c;我们可能需要给静态变量注入 spring bean&#xff0c;尝试过使用 Autowired 给静态变量做注入的同学应该都能发现注入是失败的。 Autowired 给静态变量注入bean 失败的原因 spring 底层已经限制了&#xff0c;不能给静态属性注入值&#xff1a; 如果我…

【Linux学习】深入探索进程等待与进程退出码和退出信号

文章目录 退出码return退出 进程的等待进程等待的方法 退出码 main函数的返回值&#xff1a;进程的退出码。 一般为0表示成功&#xff0c;非0表示失败。 每一个非0退出码都表示一个失败的原因&#xff1b; echo $&#xff1f;命令 作用&#xff1a;查看进程退出码。&#xf…

工具推荐:市面上有哪些带有ai问答机器人的SaaS软件

众所周知&#xff0c;SaaS&#xff08;软件即服务&#xff09;模式下的AI问答机器人已经逐渐成为企业、个人在办公、生活和学习中的辅助工具。ai问答机器人凭借高效、便捷、智能的特点&#xff0c;为用户提供了全新的交互体验。本文将推荐几款市面上好用的带有ai问答机器人的Sa…