堆详解(C语言实现)

文章目录

  • 写在前面
  • 1. 堆的概念和性质
    • 1.1 堆的概念
    • 1.2 堆的性质
  • 2 堆的实现
    • 2.1 堆结构的定义
    • 2.2 堆的初始化
    • 2.3 堆的插入
      • 2.3.1 向上调整算法
      • 2.3.2 堆的插入元素过程
    • 2.4 堆的删除
      • 2.4.1 向下调整算法
      • 2.4.2 堆的删除元素过程
    • 2.5 获取堆顶元素
    • 2.6 获取堆元素个数
    • 2.7 判断堆是否为空
    • 2.8 堆的销毁

写在前面

本片文章使用C语言实现了一种非线性的数据结构——堆(小根堆)。

1. 堆的概念和性质

1.1 堆的概念

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

画个图理解一下:
在这里插入图片描述

1.2 堆的性质

  1. 堆中某个节点的值总是不大于或不小于其父节点的值;
  2. 堆总是一棵完全二叉树。
    在这里插入图片描述

2 堆的实现

由于堆的逻辑结构是一颗完全二叉树,所以在实际存储时,通常使用数组来表示这颗完全二叉树。

数组表示法的好处:

  1. 由于完全二叉树的性质,数组表示法可以将元素按照层级顺序依次存储,不会有额外的空间浪费,使得存储更为紧凑。
  2. 数组的索引关系可以方便地表示父节点和子节点之间的关系。对于数组中的第 i 个元素,其左子节点2i+1 的位置,右子节点2i+2 的位置,而父节点在 (i−1) / 2的位置。

画个图理解一下:
在这里插入图片描述

这种数组表示法使得堆的操作更加高效,因为可以通过索引直接访问父子节点,而无需使用指针。

2.1 堆结构的定义

从上面的分析可以知道堆结构的定义通常包括一个动态开辟的数组,用来存储数据。为了方便扩容,增加一个用于记录堆中元素个数以及一个用于记录堆容量的变量。
下面是堆结构的定义:

typedef int HPDataType;
typedef struct Heap
{HPDataType* nums;//动态开辟的数组int size;//堆中元素个数int capacity;//堆容量
}Heap;

2.2 堆的初始化

在初始时,堆是一个空堆,因此将堆的元素数组指针 nums 初始化为 NULL,表示还没有分配存储空间。此时 capacity 为 0,表示堆的容量为 0,还没有存储任何元素。size 也为 0,表示当前堆中的元素个数为 0。

这种初始化状态是堆数据结构的起始状态,之后可以根据需要动态分配存储空间,并通过插入元素来逐渐构建堆的结构。
代码如下:

void HeapInit(Heap* php)
{assert(php);//检查参数有效性php->nums = NULL;//初始化为 NULL,表示还没有分配存储空间php->size = php->capacity = 0;
}

2.3 堆的插入

2.3.1 向上调整算法

在实现堆的插入之前需要介绍一下向上调整算法。这是因为向上调整算法是在堆的插入操作中使用的关键步骤。这算法确保在插入新元素后,仍然保持堆的性质
以下是向上调整算法的一般步骤:

  1. 从插入的节点开始,向上比较与父节点的大小。
  2. 如果插入节点的值大于(小于)其父节点的值,交换这两个节点。
  3. 重复步骤 2 直到满足堆的性质或达到堆的根节点。

如向该小根堆:{ 15,18,19,25,28,34,65,49,27,37 }插入一个10,并用向上调整算法使该小根堆继续保持小根堆的性质:

画个图理解一下:
在这里插入图片描述
代码如下:

//交换两个数
void Swap(HPDataType* x1, HPDataType* x2)
{HPDataType tmp = *x1;*x1 = *x2;*x2 = tmp;
}
//向上调整算法
void AdjustUp(int* nums, int child)
{while (child > 0){int parent = (child - 1) / 2;//1.小堆 向上调整,孩子小于父亲就交换//2.如果满足堆的性质,则不需要调整//若实现大根堆,这里nums[child] < nums[parent]的 < 换成 >if (nums[child] < nums[parent]){Swap(&nums[child], &nums[parent]);}else{break;}child = parent;}
}

需要注意的是:堆的向上调整算法的前提是,插入位置之前的元素已经构成了一个有效的堆。这确保了插入新元素后,通过向上调整以后,整个堆仍然是一个有效的小顶堆。

2.3.2 堆的插入元素过程

在上面向上调整算法的介绍中,我们发现向堆中插入元素时,只需把新元素追加到堆的末尾,然后通过向上调整算法,使整个堆仍然是一个有效的堆。
堆的插入元素过程通常包括以下步骤:

  1. 将新元素追加到堆的末尾。在堆的实现过程中,我们是使用数组来表示堆的,而在数组表示的堆中,堆的末尾就是数组的最后一个位置,因此新元素被添加到数组的最后一个位置。
  2. 进行向上调整。 新元素与其父节点进行比较,如果新元素的值小于其父节点的值,它们交换位置。这个过程一直重复,直到满足堆的性质,即新插入的元素不再小于其父节点或者到达堆顶。

分析:
3. 当堆为空时,插入第一个元素时,它天然满足堆的性质,因此,无需进行向上调整。
4. 当堆不为空时,插入元素时就有可能会进行向上调整。这是因为插入的新元素被追加到堆的末尾,与其父节点进行比较。如果新元素的值小于其父节点的值,就会交换它们的位置。这个过程重复进行,直到新插入的元素不再小于其父节点或者到达堆顶。

画个图理解一下:
在这里插入图片描述

代码如下:

void HeapPush(Heap* php, HPDataType x)
{assert(php);//检查参数有效性//检查是否需要扩容if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->nums, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("HeapPush->realloc");exit(-1);}php->nums = tmp;php->capacity = newcapacity;}//插入新元素php->nums[php->size++] = x;//向上调整AdjustUp(php->nums, php->size - 1);
}

2.4 堆的删除

2.4.1 向下调整算法

在实现堆的删除操作之前需要介绍一下向下调整算法。这是因为向下调整算法是在堆的删除操作中使用的关键步骤。向下调整算法通常在删除堆顶元素后使用,确保新的堆顶元素能够找到适当的位置以维持堆的有序性。

确保向下调整算法有效的前提:

堆的左右子树都满足堆的性质。这是因为向下调整算法的目的是将当前节点与其左右孩子中较小(或较大,具体取决于是小根堆还是大根堆)的值交换,以恢复堆的有序性。
如果堆的左右子树不满足堆的性质,那么通过向下调整算法进行交换可能无法保证整个堆的有序性。因此,在使用向下调整算法时,必须确保该节点的左右子树都是堆。

向下调整算法具体步骤如下:

  1. 找到较小的孩子: 如果存在左右孩子,找到值较小的孩子。如果当前节点有左孩子(位置为 2 * parent + 1),如果有右孩子(位置为 2 * parent + 2)。

  2. 比较与较小孩子的值: 将当前节点与较小孩子的值比较。

  3. 交换或结束: 如果当前节点的值大于较小孩子的值,则交换它们,并继续向下调整。如果当前节点的值小于等于较小孩子的值,则调整结束。

  4. 循环: 重复执行上述步骤,直到当前节点不再有孩子或当前节点的值小于等于孩子的值。

画个图理解一下:
在这里插入图片描述

代码如下:

//交换两个数
void Swap(HPDataType* x1, HPDataType* x2)
{HPDataType tmp = *x1;*x1 = *x2;*x2 = tmp;
}
void AdjustDown(int* nums, int n, int parent)
{// 左孩子的索引int child = parent * 2 + 1;// 循环直到没有左孩子while (child < n){// 如果右孩子存在且比左孩子小,选择右孩子//若实现大根堆,这里nums[child + 1] < nums[child]的 < 换成 >if (child + 1 < n && nums[child + 1] < nums[child]){++child;}// 如果孩子比父亲小,交换它们的值//若实现大根堆,这里nums[child] < nums[parent]的 < 换成 >if (nums[child] < nums[parent]){// 孩子比父亲大,堆的有序性已经恢复,退出循环Swap(&nums[child], &nums[parent]);}else{break;}// 更新父亲和孩子的索引parent = child;child = parent * 2 + 1;}
}

2.4.2 堆的删除元素过程

堆的删除元素过程通常包括以下步骤:

  1. 将堆顶元素与最后一个元素交换:为了方便删除堆顶元素,将堆顶元素与最后一个元素交换位置。
  2. 删除堆顶元素:将堆顶元素删除,此时堆的大小减一。
  3. 向下调整堆:由于删除了堆顶元素,需要通过向下调整算法,保持堆的有序性。向下调整的目的是将交换后的堆顶元素放置到正确的位置,使得堆仍然满足堆的性质。

画个图理解一下:
在这里插入图片描述

代码如下:

void HeapPop(Heap* php)
{assert(php);//检查参数有效性assert(!HeapEmpty(php));//检查堆是否为空Swap(&php->nums[0], &php->nums[php->size - 1]);php->size--;AdjustDown(php->nums, php->size - 1, 0);
}

2.5 获取堆顶元素

获取堆顶元素步骤:

  1. 堆不为空时,才能取堆顶数据。
  2. 获取堆顶元素的操作是直接访问堆数组的第一个元素,因为在堆的表示中,堆顶元素总是存储在数组的第一个位置。

代码如下:

HPDataType HeapTop(Heap* php)
{assert(php);//检查参数有效性assert(!HeapEmpty(php));//检查堆是否为空return php->nums[0];
}

2.6 获取堆元素个数

获取堆元素个数的操作可以直接返回堆的成员变量 size,该变量记录了堆中当前元素的个数。
代码如下:

int HeapSize(Heap* php)
{assert(php);//检查参数有效性return php->size;
}

2.7 判断堆是否为空

判断堆是否为空的操作可以通过检查堆的成员变量 size 是否为零来实现。如果 size 为零,表示堆中没有元素,即堆为空;否则,堆非空。
代码如下:

bool HeapEmpty(Heap* php)
{assert(php);//检查参数有效性return php->size == 0;
}

2.8 堆的销毁

在销毁堆的操作中,我们首先使用 free 函数释放堆中的元素数组所占用的内存,然后将数组指针设置为 NULL,并将堆的大小和容量都设置为0,表示堆已被销毁。这样的操作确保了在释放内存的同时将堆的状态清空,防止野指针和内存泄漏的问题。
代码如下:

void HeapDestroy(Heap* php)
{assert(php);//检查参数有效性free(php->nums);php->nums = NULL;php->size = php->capacity = 0;
}

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

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

相关文章

Spring Security 6.x 系列(6)—— 显式设置和修改登录态信息

一、前言 此篇是对上篇 Spring Security 6.x 系列&#xff08;5&#xff09;—— Servlet 认证体系结构介绍 中4.9章节显式调用SecurityContextRepository#saveContext进行详解分析。 二、设置和修改登录态 2.1 登录态存储形式 使用Spring Security框架&#xff0c;认证成功…

使用 ChatGPT 创建 Makefile 构建系统:从 Docker 开始

使用 Docker 搭配 ChatGPT 创建 Makefile 构建系统 Makefile 构建系统是嵌入式软件团队实现其开发流程现代化的基础。构建系统不仅允许开发人员选择各种构建目标&#xff0c;还可以将这些构建集成到持续集成/持续部署 (CI/CD) 流程中。使用诸如 ChatGPT 这样的人工智能 (AI) 工…

深度剖析API接口测试工具的企业价值

随着企业软件开发的日益复杂和互联网应用的普及&#xff0c;API接口成为不同软件系统之间信息传递的桥梁。在这一背景下&#xff0c;API接口测试工具的应用变得愈加重要&#xff0c;对企业的发展和软件质量起到了关键性的作用。本文将深入探讨API接口测试工具在企业中的重要性&…

leetcode:2133. 检查是否每一行每一列都包含全部整数(python3解法)

难度&#xff1a;简单 对一个大小为 n x n 的矩阵而言&#xff0c;如果其每一行和每一列都包含从 1 到 n 的 全部 整数&#xff08;含 1 和 n&#xff09;&#xff0c;则认为该矩阵是一个 有效 矩阵。 给你一个大小为 n x n 的整数矩阵 matrix &#xff0c;请你判断矩阵是否为一…

matlab配置

matlab配置 windowslinux挂载安装MATLAB windows 按照这里一步步配置就行( 移动硬盘中软件备份中自取) linux linux配置步骤 挂载 sudo mount -t auto -o loop /media/oyk/Elements/ubuntu/MATLAB/R2017a_glnxa64_dvd1.iso ./matlab/安装MATLAB 挂载完成后&#xff0c;先…

SpringCloudAlibaba之Nacos的持久化和高可用——详细讲解

目录 一、Nacos持久化 1.持久化说明 2.安装mysql数据库5.6.5以上版本(略) 3.修改配置文件 二、nacos高可用 1.集群说明 2.nacos集群架构图 2.集群搭建注意事项 3.集群规划 4.搭建nacos集群 5.安装Nginx 6.配置nginx conf配置文件 7.启动nginx进行测试即可 一、Nacos持久…

laravel8中常用路由使用(笔记四)

目录 1、框架路由目录统一放该目录 2、基本路由,路由都调用Route方法 3、控制器使用路由 4、路由参数 5、路由组 6、命名路由 7、命令查看当前路由列表 8、路由缓存 在Laravel 8中&#xff0c;路由定义了应用程序中接受请求的方式。它们定义了URL和相应的控制器方法之间的…

13、LCD1602调试工具

LCD1602调试工具 使用LCD1602液晶屏作为调试窗口&#xff0c;提供类似Printf函数的功能&#xff0c;可实时观察单片机内部数据的变化情况&#xff0c;便于调试和演示。 main.c #include <REGX52.H> #include "LCD1602.h" #include "Delay.h"//存储…

【开源】基于JAVA的海南旅游景点推荐系统

项目编号&#xff1a; S 023 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S023&#xff0c;文末获取源码。} 项目编号&#xff1a;S023&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四…

前端开发学习 (三) 列表功能

一、列表功能 1、列表功能 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compa…

锂电行业废水及母液除铊解决方案,除铊树脂技术

锂电池原材料和生产设备的制造、电池回收和处理等&#xff0c;产业的发展会带来铊排放问题。除了锂电池生产过 程中存在的铊污染外&#xff0c;企业的生活污水或者初期雨水也含有铊&#xff0c;因为铊是一种广泛存在于自然环境中的 元素&#xff0c;存在于饮用水、土壤和食物中…

高效视频剪辑:按指定时长批量分割视频,释放无尽创意

随着数字媒体技术的不断发展&#xff0c;视频剪辑已经成为日常生活中不可或缺的一部分。无论是制作电影、电视剧&#xff0c;还是创意生活短视频&#xff0c;视频剪辑都扮演着重要的角色。然而&#xff0c;对于许多非专业人士来说&#xff0c;视频剪辑可能是一项复杂而耗时的任…

27.0/多态/对象向上转型/向下转型/抽象类/抽象方法。

目录 27.1为什么使用多态? 27.1.2什么是多态 27.1.3对象多态 27.1.4多态的使用前提 27.2 向上转型 27.3向下转型 (面试题) 27.4抽象类和抽象方法 特点(面试题): 27.1为什么使用多态? 需求1&#xff1a;动物园让我们实现一个功能&#xff1a; 创建一个狗类 &#xff0c;狗…

Leetcode—739.每日温度【中等】

2023每日刷题&#xff08;四十二&#xff09; Leetcode—739.每日温度 单调栈实现思想 从右到左实现代码 class Solution { public:vector<int> dailyTemperatures(vector<int>& temperatures) {int n temperatures.size();stack<int> st;vector<i…

ensp 启动设备时报40错误,然后一直没有去管,再次进去就好了,我知道是配置虚拟机的时候修改了一些设置:

第一个阶段&#xff1a; 那时我是重置电脑之后就安装了ensp所以没有出现什么问题&#xff0c;&#xff08;那时没有导入ce6800和12800还有防火墙6000&#xff09; 第二个阶段&#xff1a; 因为有华为相关的实验要做&#xff0c;所以心血来潮打开了ensp&#xff08;路由器之前…

Digicert OV 代码签名介绍

Digicert OV 代码签名证书是一种数字证书&#xff0c;用于对软件代码进行数字签名。数字签名是一种验证软件来源和完整性的技术&#xff0c;通过使用私有密钥对代码进行签名&#xff0c;并在签名后使用公共密钥验证签名。 可基于更多平台&#xff0c;最大限度地提高分发量和收…

03、K-means聚类实现步骤与基于K-means聚类的图像压缩(2)

03、K-means聚类实现步骤与基于K-means聚类的图像压缩&#xff08;2&#xff09; 工程下载&#xff1a;K-means聚类实现步骤与基于K-means聚类的图像压缩 其他&#xff1a; 03、K-means聚类实现步骤与基于K-means聚类的图像压缩&#xff08;1&#xff09; 03、K-means聚类实现…

Linux 命令ln

1什么是链接 ln在Linux中 ln 命令的功能是为某一个文件在另外一个位置建立一个同步的链接&#xff0c;当我们需要在不同的目录&#xff0c;用到相同的文件时&#xff0c;我们不需要在每一个需要的目录下都放一个必须相同的文件&#xff0c;我们只要在某个固定的目录&#xff0…

SpringBoot监控Redis事件通知

Redis的事件通知 Redis事件通过 Redis 的订阅与发布功能&#xff08;pub/sub&#xff09;来进行分发&#xff0c; 因此所有支持订阅与发布功能的客户端都可以在无须做任何修改的情况下&#xff0c; 使用键空间通知功能。 因为 Redis 目前的订阅与发布功能采取的是发送即忘&am…

Python爬虫入门课: 如何实现数据抓取 <文字 图片 音频 视频 文档..>

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 环境使用: Python 3.10 解释器 Pycharm 编辑器 模块使用: requests re csv pandas 爬虫实现第一步: 一. 抓包分析 找到对应数据链接地址 套用代码: 修改…