深入理解堆(Heap):一个强大的数据结构

在这里插入图片描述在这里插入图片描述

.

个人主页:晓风飞
专栏:数据结构|Linux|C语言
路漫漫其修远兮,吾将上下而求索


文章目录

  • 前言
  • 堆的实现
    • 基本操作
      • 结构体定义
      • 初始化堆(HeapInit)
      • 销毁堆(HeapDestroy)
    • 重要函数
      • 交换函数(Swap)
      • 上浮调整(UpAdd)
      • 下沉调整(DnAdd)
    • 重要操作
      • 向堆中插入元素(HeapPush)
      • 从堆中弹出元素(HeapPop)
  • 堆的应用
  • 完整代码
  • 结语

前言

在计算机科学中,堆(Heap) 是一种非常重要的数据结构,广泛用于各种应用,从数据分析到算法优化,再到系统编程。堆的一个关键特性是其能够快速找到一组数中的最大或最小值。但是,什么是堆?如何在实际编程中实现和使用堆呢?


堆的实现

堆是一种特殊的完全二叉树。在一个最小堆中,每个父节点的值都小于或等于其子节点的值;相反,在一个最大堆中,每个父节点的值都大于或等于其子节点的值。这种属性使得堆能够快速访问、添加和删除其最大或最小元素。

在这里插入图片描述

在这里插入图片描述

基本操作

结构体定义

首先,堆是通过以下结构体定义的:

typedef struct Heap
{HPDataType* a;  // 指向堆数组的指针size_t size;    // 堆当前的大小int capacity;   // 堆的最大容量
} Hp;

这里,HPDataType* a 是一个指向动态分配数组的指针,该数组用于存储堆中的元素。size 表示堆中当前元素的数量,而 capacity是数组的最大容量。

初始化堆(HeapInit)

初始化函数 HeapInit 用于设置堆的初始状态:

// 初始化堆
void HeapInit(Hp* hp)
{assert(hp);  // 确保堆指针有效hp->a = NULL;  // 堆数组指针置空hp->size = 0;  // 初始化堆大小为0hp->capacity = 0;  // 初始化堆容量为0
}

此函数确保堆的开始状态为空,没有分配任何内存,且大小和容量都为零。

销毁堆(HeapDestroy)

为了避免内存泄漏,当堆不再需要时,应该使用 HeapDestroy 函数来释放其占用的内存:

// 销毁堆
void HeapDestroy(Hp* hp)
{assert(hp);  // 确保堆指针有效free(hp->a);  // 释放堆数组内存hp->a = NULL;  // 将堆数组指针置空hp->size = hp->capacity = 0;  // 重置堆大小和容量为0
}

这个函数释放了堆数组 a 的内存,并将指针置空,同时重置大小和容量。

重要函数

交换函数(Swap)

Swap 函数用于交换堆中两个元素的值。这在上浮和下沉调整中非常重要。

// 交换两个元素的值
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = 0;  // 临时变量用于交换tmp = *p1;*p1 = *p2;*p2 = tmp;
}

上浮调整(UpAdd)

这个函数通过一个临时变量 tmp 实现了两个元素值的交换。当一个新元素被加入到堆中时,它可能会破坏堆的特性(在最小堆中,所有父节点的值都应该小于其子节点的值)。UpAdd 函数通过将新加入的元素与其父节点进行比较和交换来修复这种情况,直到堆的特性得到恢复或元素到达根位置。

// 上浮调整
void UpAdd(HPDataType* a, HPDataType 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;  // 如果不需要交换,则终止循环}}
}

这个过程被称为“上浮”,因为较小的元素(在最小堆中)浮向堆的顶部。
在这里插入图片描述

下沉调整(DnAdd)

与上浮调整相反,当堆顶元素被移除后,我们需要从堆的顶部开始将新的根元素下移,以保持堆的特性。这是通过将父节点与其子节点比较并在必要时进行交换来实现的。

// 下沉调整
void DnAdd(HPDataType* a, HPDataType parent, int size)
{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;  // 如果不需要交换,则终止循环}}
}

这个过程被称为“下沉”,因为较大的元素(在最小堆中)下沉到堆的底部。
在这里插入图片描述

重要操作

向堆中插入元素(HeapPush)

插入操作是堆的核心功能之一。HeapPush 函数首先检查是否需要扩展堆的容量,然后将新元素添加到堆的末尾,并进行上浮调整以保持堆的特性:

// 向堆中插入元素
void HeapPush(Hp* 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, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");  // 内存分配失败exit(-1);}hp->a = tmp;hp->capacity = newcapacity;}hp->a[hp->size] = x;  // 插入元素hp->size++;  // 堆大小增加UpAdd(hp->a, hp->size - 1);  // 上浮调整
}

从堆中弹出元素(HeapPop)

HeapPop 函数移除堆顶元素,这通常是堆中的最小或最大值。在移除元素后,它执行下沉调整以保持堆的特性:

// 从堆中弹出元素
void HeapPop(Hp* hp)
{assert(hp);  // 确保堆指针有效assert(hp->size > 0);  // 确保堆非空Swap(&hp->a[0], &hp->a[hp->size - 1]);  // 交换堆顶和堆底元素hp->size--;  // 减小堆大小DnAdd(hp->a, 0, hp->size);  // 下沉调整
}

上浮和下沉调整
上浮(UpAdd)和下沉(DnAdd)调整是维护堆特性的关键操作。上浮调整用于在添加新元素后恢复堆的特性,而下沉调整则在移除顶部元素后用于恢复堆的特性。

堆的应用

堆在多种场景中都非常有用。例如,在优先队列、堆排序、图算法(如Dijkstra的最短路径算法)中,堆结构都扮演着核心角色。它能够优化这些算法的性能,特别是在处理大数据集时。

如何使用堆?
使用堆的一个典型例子是维护动态数据集的最小或最大元素。以下是使用C语言实现的堆如何工作的一个简单示例:

int main() {int a[] = {4, 6, 2, 1, 5, 8, 2, 9};Hp hp;HeapInit(&hp);// 将数组元素插入堆中for (int i = 0; i < sizeof(a) / sizeof(int); ++i) {HeapPush(&hp, a[i]);}// 弹出并打印堆顶元素,直到堆为空while (!HeapEmpty(&hp)) {printf("%d ", HeapTop(&hp));HeapPop(&hp);}return 0;
}

也可以用堆输出数组a中最大的前三个数

	int k = 3;while (k--){printf("%d\n", HeapTop(&hp));HeapPop(&hp);}

完整代码

可以来我的github参观参观,看完整代码
路径点击这里–>数据结构堆的实现练习

结语

希望这篇文章能够帮助您清晰地理解堆的结构和功能,并鼓励您在未来的项目中尝试和探索它。理解数据结构的关键不仅在于记住它们的运作方式,而且在于知道何时以及如何使用它们。无论您是一位经验丰富的程序员还是刚开始编程之旅的新手,掌握数据结构总是一项宝贵的技能。因此,拿起您的编程工具,开始构建、优化,最重要的是,享受编程带来的无限可能吧!

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

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

相关文章

Talk | EMNLP 2023 最佳长论文:以标签为锚-从信息流动的视角分析上下文学习

本期为TechBeat人工智能社区第561期线上Talk。 北京时间1月4日(周四)20:00&#xff0c;北京大学博士生—王乐安的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “以标签为锚-从信息流动的视角分析上下文学习”&#xff0c;介绍了他的团队在上下文学…

STM32深入系列02——BootLoader分析与实现

文章目录 1. STM32程序升级方法1.1 ST-Link / J-link下载1.2 ISP&#xff08;In System Programing&#xff09;1.3 IAP&#xff08;In Applicating Programing&#xff09;1.3.1 正常程序运行流程1.3.2 有IAP时程序运行流程 2. STM32 Bootloader实现2.1 方式一&#xff1a;Boo…

Qt/QML编程学习之心得:Linux下Thread线程创建(26)

GUI设计中经常为了不将界面卡死,会用到线程Thread,而作为GUI设计工具,Qt也提供了一个这样的类,即QThread。 QThread对象管理程序中的一个控制线程。线程QThread开始在run()中执行。默认情况下,run()通过调用exec()启动事件循环,并在线程内运行Qt事件循环。 也可以通过…

Java泛型中的T,R,K,V,E:一探究竟

Java的泛型&#xff0c;作为一种强大的编程工具&#xff0c;让代码更具复用性&#xff0c;同时避免了类型转换的繁琐操作。在使用泛型时&#xff0c;我们经常会遇到一些特定的类型参数符号&#xff0c;如T、R、K、V、E等。那么这些符号代表的是什么呢&#xff1f;下面&#xff…

【第6期】使用Iview的Select组件进行远程搜索并在编辑时设置一个或多个默认值

本期简介 下拉框这个组件用的地方非常多&#xff0c;普通用法就是将数据列表一次性查询渲染&#xff0c;在列表里面直接本地搜索&#xff0c;优点是可缓存、速度快&#xff0c;但在某些场合并不适用&#xff0c;比如要在下拉框中选择一所中国的学校&#xff0c;幼儿园/小学/初…

20240107查看Android11下移远的4G模块EC20在Firefly的AIO-3399J开发板跑通时的相关服务

20240107查看Android11下移远的4G模块EC20在Firefly的AIO-3399J开发板跑通时的相关服务 2024/1/7 11:24 缘起&#xff1a;友善之臂的SDK&#xff1a;rk3399-android-11-r20211216.tar.xz可以跑通EC20&#xff0c;但是Toybrick的不行&#xff01; 同样是Andrid11&#xff0c;因此…

【前端】下载文件方法

1.window.open 我最初使用的方法就是这个&#xff0c;只要提供了文件的服务器地址&#xff0c;使用window.open也就是在新窗口打开&#xff0c;这时浏览器会自动执行下载。 2.a标签 其实window.open和a标签是一样的&#xff0c;只是a标签是要用户点击触发&#xff0c;而wind…

Python的核心知识点整理大全66(已完结撒花)

目录 D.3 忽略文件 .gitignore 注意 D.4 初始化仓库 D.5 检查状态 D.6 将文件加入到仓库中 D.7 执行提交 D.8 查看提交历史 D.9 第二次提交 hello_world.py D.10 撤销修改 hello_world.py 注意 D.11 检出以前的提交 往期快速传送门&#x1f446;&#xff08;在文…

外贸独立站建站详细操作流程一览,跨境电商卖家营销必看!

独立站是一个独立的网站&#xff0c;包括有独立的服务器&#xff0c;独立的网站程序以及网站域名。关于独立站的优势已经说了很多&#xff0c;本文就不再细谈&#xff0c;想了解的小伙伴可以自行查找之前发布的文章观看。 今天就来说说搭建独立站的详细步骤都有哪些&#xff1f…

FlinkRestAPI

which flink 找到Flink客户端地址 如果输出结果为空&#xff0c;则说明 Flink 客户端没有安装在系统路径中。在这种情况下&#xff0c;您可以通过设置 FLINK_HOME 环境变量来指定 Flink 客户端的路径。例如&#xff1a; export FLINK_HOME/opt/flink 然后&#xff0c;您可以使…

Docker mysql 主从复制

目录 介绍&#xff1a;为什么需要进行mysql的主从复制 主从复制原理&#xff1a; ✨主从环境搭建 主从一般面试问题&#xff1a; 介绍&#xff1a;为什么需要进行mysql的主从复制 在实际的生产中&#xff0c;为了解决Mysql的单点故障已经提高MySQL的整体服务性能&#xff…

【AI视野·今日NLP 自然语言处理论文速览 第七十期】Thu, 4 Jan 2024

AI视野今日CS.NLP 自然语言处理论文速览 Thu, 4 Jan 2024 Totally 29 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Multilingual Instruction Tuning With Just a Pinch of Multilinguality Authors Uri Shaham, Jonathan Herzi…

英飞凌TC3xx之一起认识GTM(十一)详细说说GTM子模块TIM(TIM通道模式)

英飞凌TC3xx之一起认识GTM(十一)详细说说GTM子模块TIM(TIM通道模式) 1 PWM 测量模式TPWM2 脉冲积分模式TPIM3 输入事件模式TIEM4 输入预分频器模式TIPM5 位压缩模式TBCM6 门控定期采样模式TGPS7 串行移位模式TSSM8 常见应用举例8.1 如何配置TPIM模式8.2 如何配置TIEM模式8.…

Python3 列表--20240104

Python3 列表 序列是 Python 中最基本的数据结构。 序列中的每个值都有对应的位置值,称之为索引,第一个索引是 0,第二个索引是 1,依此类推。 Python 有 6 个序列的内置类型,但最常见的是列表和元组。 列表都可以进行的操作包括索引,切片,加,乘,检查成员。 此外,Pyth…

【c++】map

文章目录 1.创建map对象2.使用下标访问map对象3.map::insert的使用4.查找5.删除6.遍历 1.创建map对象 对象操作功能map<k, v> m;创建一个名为m的空map对象&#xff0c;键类型为k, 值类型为v。map<k, v> m(m2)创建m2的副本map<k, v> m(b, e)创建map类型的对象…

3D点云平面拟合算法

假设你有一组 3D 中的 n 个点&#xff0c;并且想要为它们拟合一个平面。 在本文中&#xff0c;我将推导出一个简单的、数值稳定的方法&#xff0c;并提供它的源代码。 听起来很好玩&#xff1f; 我们开始吧&#xff01; NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO…

阿里云服务器“可用区”是什么意思?

阿里云可用区是什么意思&#xff1f;可用区是同一个地域下电力和网络相互独立的区域&#xff0c;可用区是用来搭建高可用高容灾应用架构的&#xff0c;因为可用区之间可以做到故障隔离。阿里云服务器网aliyunfuwuqi.com简单说下可用区什么是可用区及可用区的选择方法&#xff1…

简易计算器的实现(MFC)

文章目录 1.预备知识1.简单计算器的实现2.快捷键1.全局快捷键1.创建2.注销 2.局部快捷键 3.计算机内部浮点数的存储1.浮点数表示格式2.浮点数的计算3.实例 2.实验目的3.实验内容4.代码实现1.界面编写2.准备工作3.数字输入4.特殊数字输入&#xff08;指数与小数&#xff09;1.指…

ubuntu安装docker指定版本

ubuntu安装docker指定版本 https://docs.docker.com/engine/install/ubuntu/ 安装apt源 # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.d…

计算机组成原理 I/O方式

I/O 方式 I/O方式分类: 程序查询方式。由 CPU通过程序不断查询 /O 设备是否已做好准备&#xff0c;从而控制0 设备与主机交换信息程序中断方式。只在 I/0 设备准备就绪并向 CPU发出中断请求时才予以响应。DMA方式。主存和 I/O 设备之间有一条直接数据通路&#xff0c;当主存和…