【数据结构】【C语言】堆~动画超详细解读!

news2024/11/15 4:32:11

目录

    • 1 什么是堆
      • 1.1 堆的逻辑结构和物理结构
      • 1.2 堆的访问
      • 1.3 堆为什么物理结构上要用数组?
      • 1.4 堆数据上的特点
    • 2 堆的实现
      • 2.1 堆类型定义
      • 2.2 需要实现的接口
      • 2.3 初始化堆
      • 2.4 销毁堆
      • 2.5 堆判空
      • 2.6 交换函数
      • 2.7 向上调整(小堆)
      • 2.8 向下调整(小堆)
      • 2.9 堆插入
      • 2.10 堆删除
      • 2.11 //堆顶
    • 3 完整代码
      • 3.1 heap.h
      • 3.2 heap.c

1 什么是堆

  • 简单来说堆是二叉树的一种表示方式,它在逻辑上就是一颗完全二叉树,它在物理上却是一个数组,这么说可能有点抽象,我们原来学习的栈,队列,或者说顺序表,链表等等,他们的逻辑结构和物理结构是相同或者相似的,就会比较好理解一些,而在堆这里物理结构和逻辑结构截然不同,理解相对就会比较抽象一些,我们接着看

1.1 堆的逻辑结构和物理结构

  • 逻辑结构即我们想象的结构,就比方说我们早上在图书馆排队的时候,放个包在图书馆门口,人可能都不见了,这个时候我们逻辑上认为我们在排队,但物理上我们同学就可能在吃早饭上厕所啥的
  • 逻辑上我们想象这个数组是一个二叉树,并且像二叉树一样访问子节点或者父节点
  • 比方说我给出以下数组,它在逻辑上是这样表示的(当然哈,指针其实是不存在的,只是逻辑上我们看作其是父子关系):
    请添加图片描述

1.2 堆的访问

  • 既然堆是一颗货真价实的二叉树,可我们怎么像二叉树一样,通过父/子节点访问子/父节点呢?

在这里插入图片描述

  1. 通过父节点访问子节点:
    • 我们假设父节点的下标为3,我们想访问它的子节点,只需要把 父节点的下标 * 2 + 1父节点的下标 * 2 + 2 即可 即 7 或 8
  2. 通过子节点访问父节点
    • 我们假设子节点的下标为7,我们想访问它的父节点,只需要把 (子节点的下标 - 1) / 2 即可 即 3
    • 我们假设子节点的下表为8,我们想访问它的父节点,依旧只需要把 (子节点的下标 - 1) / 2 即可 依旧是 3

1.3 堆为什么物理结构上要用数组?

  • 事实上学习堆是为了学习堆排序打基础,在堆排序中,有时候需要频繁交换头尾节点,如果用数组,找节点就会方便很多,交换函数也很好写,效率会更高,用链表要不断去遍历,或者专门写个尾指针妥协,很没必要
  • 其次,如果我们用链式存储的话,访问子/父节点需要定义3个指针,需要多开辟很多空间
  • 堆一定是完全二叉树,用数组存放会很方便,其中不会有空节点,所有数据存储都是连续的

1.4 堆数据上的特点

  • 堆必须要始终满足满足:父节点值比子节点小或者父节点始终比子节点大
  • 我们称父节点值始终比子节点小的堆为小堆/小根堆
  • 我们称父节点值始终比子节点大的堆为大堆/大根堆

例如:
1.大堆:
在这里插入图片描述

2.小堆:
在这里插入图片描述

2 堆的实现

2.1 堆类型定义

//堆在物理上是一个数组,我们直接按数组的定义方式就行
//类型定义
typedef int HPDataType;

typedef struct Heap 
{
	HPDataType* data;
	int size;
	int capa;
}Heap;

2.2 需要实现的接口

//交换函数
void Swap(HPDataType* x, HPDataType* y);

//向上调整(小堆)
void AdjustUp(HPDataType* data, int child);

//向下调整(小堆)
void AdjustDown(HPDataType* data, int size, int father);

//初始化堆
void HPInit(Heap* php);

//销毁堆
void HPDestroy(Heap* php);

//堆插入
void HPPush(Heap* php, HPDataType x);

//堆删除
void HPPop(Heap* php);

//堆顶
HPDataType HPTop(Heap* php);

//堆判空
bool HPEmpty(Heap* php);

2.3 初始化堆

//像顺序表一样初始化就行
//初始化堆
void HPInit(HP* php)
{
	assert(php);
	php->a = (HPdatatype*)calloc(4, sizeof(HPdatatype));
	if (php->a == NULL)
	{
		perror("HPInit::calloc fail");
		exit(1);
	}
	php->capa = 4;
	php->size = 0;
}

2.4 销毁堆

//同样,像顺序表一样销毁就行
//销毁堆
void HPdestory(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capa = 0;
	php->size = 0;
}

2.5 堆判空

//堆判空
bool HPEmpty(HP* php)
{
	assert(php);   //判空
	return php->size == 0;    //size是0就返回true
}

2.6 交换函数

//只是完成数据在空间上的交换
//交换函数
void Swap(HPdatatype* x, HPdatatype* y)
{
	HPdatatype tmp = *x;
	*x = *y;
	*y = tmp;
}

2.7 向上调整(小堆)

  • 注意这里是重点
  • 向上调整会在很多地方用到,基本思想就是让本应该在上面的节点往上挪
//向上调整(小堆)
void AdjustUp(HPdatatype* a, int child)
{
	assert(a);//判空

	int father = (child - 1) / 2;//推算出父节点的位置
	while (father < child && a[father] > a[child])      //只要子节点比父节点还小,就让子节点和父节点交换
	{								               		//重复此步骤直到子节点大于父节点或者子节点和自己比较
		Swap(&a[child], &a[father]); 
		child = father;
		father = (child - 1) / 2;
	}
}

请添加图片描述

2.8 向下调整(小堆)

  • 注意这里是重点
  • 向下调整同样会在很多地方用到
//向下调整(小堆)
void AdjustDown(HPdatatype* a, int size, int father)
{
	assert(a);//判空

	int child = (father * 2) + 1;      //先假设比较小的是左子节点
	if (child + 1 < size && a[child] > a[child + 1])        //如果右子节点比左子节点大,注意要判断一下子节点是否会超范围
	{
		child++;        //把child改成右子节点
	}
	while (child < size && a[father] > a[child] )    //如果父节点一直比子节点大就不断交换下移
	{                                                //直到子节点超出size范围或者父节点比子节点小就停下
		Swap(&a[father], &a[child]);     //交换

		father = child;              //重新找到父节点(交换后的父节点应该是原来的子节点的位置)
		child = (father * 2) + 1;    //重新定位子节点
		if (child + 1 < size && a[child] > a[child + 1])    //如法炮制
		{
			child++;
		}
	}
}

请添加图片描述

2.9 堆插入

  • 堆插入一般插入到末尾,因为末尾很空,啥也没有,比较好插入,插在其他地方还得先挪动才可以插入
//堆插入
void HPPush(HP* php, HPdatatype x)
{
	assert(php);   //判空

	//堆扩容,这里像数组一样扩容就行
	if (php->capa == php->size)
	{
		HPdatatype* tmp = (HPdatatype*)realloc(php->a, 2 * php->size * sizeof(HPdatatype));
		if (tmp == NULL)
		{
			perror("HPPush::realloc fail");
			exit(1);
		}
		php->a = tmp;
		php->capa *= 2;
	}

	php->a[php->size] = x;    //将要插入的数据放到堆低
	
	AdjustUp(php->a, php->size);      //通过向上调整找到这个数据本应该在的位置

	php->size++;      //别忘了让size++
}

2.10 堆删除

  • 堆删除一般删除堆顶的数据,但不能简单地把堆顶置为空,而是要和末尾数据交换,再一点点下调
//堆删除
void HPPop(HP* php)
{
	assert(php);     //判空
	if (!HPEmpty(php))    //如果堆不是空堆
	{
		Swap(&php->a[0], &php->a[php->size - 1]);   //交换堆顶和末尾的数据

		AdjustDown(php->a, php->size - 1, 0);    //将堆顶的数据向下挪到合适的位置

		php->size--;   //别忘了size--
	}
}

2.11 //堆顶

//取堆顶
HPdatatype HPTop(HP* php)
{
	assert(php);    //判空
	return HPEmpty(php) ? -1 : php->a[0];   //返回堆顶数据
}

  • 完整代码在最下面哦

佬!都看到这了,如果觉得有帮助的话一定要点赞啊佬 >v< !!!
放个卡密在这,感谢各位能看到这儿啦!
请添加图片描述


3 完整代码

3.1 heap.h

#pragma once

//头文件声明
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>

//类型定义
typedef int HPdatatype;

typedef struct Heap
{
	HPdatatype* a;
	int size;
	int capa;
}HP;

//函数申明

//交换函数
void Swap(HPDataType* x, HPDataType* y);

//向上调整(小堆)
void AdjustUp(HPDataType* data, int child);

//向下调整(小堆)
void AdjustDown(HPDataType* data, int size, int father);

//初始化堆
void HPInit(Heap* php);

//销毁堆
void HPDestroy(Heap* php);

//堆插入
void HPPush(Heap* php, HPDataType x);

//堆删除
void HPPop(Heap* php);

//堆顶
HPDataType HPTop(Heap* php);

//堆判空
bool HPEmpty(Heap* php);

3.2 heap.c

#include "heap.h"

//初始化堆
void HPInit(HP* php)
{
	assert(php);
	php->a = (HPdatatype*)calloc(4, sizeof(HPdatatype));
	if (php->a == NULL)
	{
		perror("HPInit::calloc fail");
		exit(1);
	}
	php->capa = 4;
	php->size = 0;
}

//销毁堆
void HPdestory(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capa = 0;
	php->size = 0;
}

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

//取堆顶
HPdatatype HPTop(HP* php)
{
	assert(php);
	return HPEmpty(php) ? -1 : php->a[0];
}

//交换
void Swap(HPdatatype* x, HPdatatype* y)
{
	HPdatatype tmp = *x;
	*x = *y;
	*y = tmp;
}

//向上调整(小堆)
void AdjustUp(HPdatatype* a, int child)
{
	assert(a);

	int father = (child - 1) / 2;
	while (father < child && a[father] > a[child])
	{
		Swap(&a[child], &a[father]);
		child = father;
		father = (child - 1) / 2;
	}
}

//堆插入
void HPPush(HP* php, HPdatatype x)
{
	assert(php);

	//堆扩容
	if (php->capa == php->size)
	{
		HPdatatype* tmp = (HPdatatype*)realloc(php->a, 2 * php->size * sizeof(HPdatatype));
		if (tmp == NULL)
		{
			perror("HPPush::realloc fail");
			exit(1);
		}
		php->a = tmp;
		php->capa *= 2;
	}

	php->a[php->size] = x;
	
	AdjustUp(php->a, php->size);

	php->size++;
}

//向下调整(小堆)
void AdjustDown(HPdatatype* a, int size, int father)
{
	assert(a);

	int child = (father * 2) + 1;
	if (child + 1 < size && a[child] > a[child + 1])
	{
		child++;
	}
	while (child < size && a[father] > a[child] )
	{
		Swap(&a[father], &a[child]);

		father = child;
		child = (father * 2) + 1;
		if (child + 1 < size && a[child] > a[child + 1])
		{
			child++;
		}
	}
}

//堆删除
void HPPop(HP* php)
{
	assert(php);
	if (!HPEmpty(php))
	{
		Swap(&php->a[0], &php->a[php->size - 1]);

		AdjustDown(php->a, php->size - 1, 0);

		php->size--;
	}
}

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

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

相关文章

Flink常见面试题总结

文章目录 1. 简单介绍一下Flink2. Flink 的运行必须依赖Hadoop组件吗?3. Flink 和 Spark Streaming 的区别&#xff1f;4. Flink集群角色5. Flink核心概念5.1 并行度5.2 算子链&#xff08;Operator Chain&#xff09;5.3 任务槽&#xff08;Task Slots&#xff09;5.4 任务槽…

Python | Leetcode Python题解之第108题将有序数组转换为二叉搜索树

题目&#xff1a; 题解&#xff1a; class Solution:def sortedArrayToBST(self, nums: List[int]) -> TreeNode:def helper(left, right):if left > right:return None# 选择任意一个中间位置数字作为根节点mid (left right randint(0, 1)) // 2root TreeNode(nums…

cesium绘制三角网可视化及mesh网格数据解析

可视化运行效果(水质污染扩散) 实现运行效果 术语 Mesh网格数据解析 Mesh&#xff08;网格&#xff09;在不同领域有不同的应用和定义。在计算机网络中&#xff0c;Mesh网络指的是一种无中心的网状结构&#xff0c;每个节点都与其他节点相连。而在3D计算机图形学中&#…

计算机专业实习生应该去哪实习?

计算机专业实习生可以选择在各种不同类型的公司和组织中实习。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我在后台发给你。 这取…

局部直方图均衡化去雾算法

目录 1. 引言 2. 算法流程 3. 代码 4. 去雾效果 1. 引言 局部直方图算法是一种基于块的图像去雾方法&#xff0c;它将图像分割为若干个块&#xff0c;并在每个块内计算块的局部直方图。通过对各个块的直方图进行分析和处理&#xff0c;该算法能够更好地适应图像中不同区域的…

聚鼎科技:现在的装饰画怎么样

当代装饰画以其多样化的风格和形式&#xff0c;在装点生活的同时反映了现代人的审美趣味和文化追求。如今&#xff0c;装饰画不再局限于传统的油画、水彩或版画&#xff0c;它们已经跨越了材质与技法的界限&#xff0c;呈现出前所未有的丰富性。 走进任一家装潢精致的咖啡馆或现…

基于机器学习模型预测信用卡潜在用户(XGBoost、LightGBM和Random Forest)

基于机器学习模型预测信用卡潜在用户&#xff08;XGBoost、LightGBM和Random Forest&#xff09; 随着数据科学和机器学习的发展&#xff0c;越来越多的企业开始利用这些技术来提高运营效率。在这篇博客中&#xff0c;我将分享如何利用机器学习模型来预测信用卡的潜在客户。此…

angr使用学习

首先我是直接在kali中安装的&#xff0c;也是边练边学的。 嗯&#xff0c;要在纯净python环境&#xff0c;所以是在 virtualenv 虚拟环境里&#xff0c;也不是特别会用这个&#xff0c;按照教程一步步做的 source venv/bin/activate 进入了对应环境 退出是 deactivate en,ipy…

pdf文件怎么编辑?分享3个专业的pdf软件!

在数字化时代&#xff0c;PDF文件已成为我们工作、学习中的得力助手。然而&#xff0c;面对需要修改的PDF文件&#xff0c;许多人却感到无从下手。今天&#xff0c;就让我们一起探索如何轻松编辑PDF文件&#xff0c;并介绍几款实用的编辑软件&#xff0c;让你轻松应对各种PDF编…

Spring:IoC容器(基于注解管理bean)

1. HelloWorld * 引入依赖* 开启组件扫描* 使用注解定义 Bean* 依赖注入 2.开启组件扫描 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/20…

免费开源人脸识别系统,支持RESTful API

简介 CompreFace 是一个免费开源的人脸识别项目&#xff0c;您不需要具备机器学习技能就能安装设置和使用 CompreFace&#xff0c;官方提供了基于 docker 的部署方法&#xff0c;可以方便地部署在本地或者云端服务器上。 CompreFace 提供了 RESTful API&#xff0c;用于人脸识别…

大模型效能工具之智能CommitMessage

01 背景 随着大型语言模型的迅猛增长&#xff0c;各种模型在各个领域的应用如雨后春笋般迅速涌现。在研发全流程的效能方面&#xff0c;也出现了一系列贯穿全流程的提效和质量工具&#xff0c;比如针对成本较高的Oncall&#xff0c;首先出现了高质量的RAG助手&#xff1b;在开…

云原生Kubernetes: K8S 1.26版本 部署KubeSphere

目录 一、实验 1.环境 2.K8S 1.26版本部署HELM 3.K8S 1.26版本 部署KubeSphere 4.安装KubeSphere DevOps 二、问题 1.如何安装Zadig 2.扩展插件Zadig安装失败 3.calico 如何实现不同node通信 4.如何清除docker占用的磁盘空间 5.如何强制删除资源 6.namespace删除不…

linux命令中arj使用

arj 用于创建和管理.arj压缩包 补充说明 arj命令 是 .arj 格式的压缩文件的管理器&#xff0c;用于创建和管理 .arj 压缩包。 语法 arj(参数)参数 操作指令&#xff1a;对 .arj 压缩包执行的操作指令&#xff1b;压缩包名称&#xff1a;指定要操作的arj压缩包名称。 更多…

非关系型数据库NOSQL

文章目录 1. NOSQL 概述2. 相关理论基础2.1 一致性2.2 分区2.3 存储分布2.4 查询模型 3. NOSQL 数据库的种类3.1 文档存储3.2 键值存储3.3 列存储3.3 图存储 4. NOSQL 应用案例和新技术4.1 HBase 数据库4.2 云数据库 GeminiDB 非关系型的数据库 NOSQL (Not Only SQL)是对不同于…

2024.05.23 学习记录

1、 react hooks 面经复习 2、xiaolin coding 计算机网络 复习 3、组件库 subMenu、test测试、tabs组件初步开发完成 4、代码随想录刷题&#xff1a;动态规划 01背包 all

sharded jedis pipelined 执行后 数据并未存入redis

前言 因为历史原因&#xff0c;在某个同步菜单操作的方法中先清除缓存&#xff0c;然后在初始化缓存。本来很正常的逻辑&#xff0c;但是这个清除是db查询获取所有的菜单 然后循环一条条删除 然后在db查询有效的菜单操作 在循环一条条插进去 经统计这个菜单操作大概有个7千个 …

实战Java虚拟机-高级篇

一、GraalVM 什么是GraalVM GraalVM是Oracle官方推出的一款高性能JDK&#xff0c;使用它享受比OpenJDK或者OracleJDK更好的性能。GraalVM的官方网址&#xff1a;https://www.graalvm.org/官方标语&#xff1a;Build faster, smaller, leaner applications。 更低的CPU、内存…

化简资源分配图判断是否发生死锁

目录 1.资源分配图的概念 2.判断是否发生死锁 1.资源分配图的概念 资源分配图表示进程和资源之间的请求关系&#xff0c;例如下图&#xff1a; P代表进程&#xff0c;R代表资源&#xff0c;R方框中 有几个圆球就表示有几个这种资源&#xff0c;在图中&#xff0c;R1指向P1&a…