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

news2024/9/25 19:21:01

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

.

个人主页:晓风飞
专栏:数据结构|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;  // 初始化堆大小为0
  hp->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.coloradmin.cn/o/1364420.html

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

相关文章

Mybatis简易搭建并查询数据库表内所有数据

搭建步骤 1.在mysql中创建user表&#xff0c;添加数据2.创建maven模块&#xff0c;导入坐标3.在资源文件夹中导入需要用到的logback.xml4.编写MyBatis 核心配置文件->替换连接信息 解决硬编码问题5.编写 SQL映射文件->统一管理sgl语句&#xff0c;解决硬编码问题6.编码定…

Mybatis实现增删改查的两种方式-配置文件/注解

环境准备 1.数据库表tb_brand -- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand(-- id 主键id int primary key auto_increment,-- 品牌名称brand_name varchar(20),-- 企业名称company_name varchar(20),-- 排序字段ordered int…

阿里巴巴微服务治理框架的终极PK!

另外我的新书RocketMQ消息中间件实战派上下册&#xff0c;在京东已经上架啦&#xff0c;目前都是5折&#xff0c;非常的实惠。 https://item.jd.com/14337086.html​编辑https://item.jd.com/14337086.html “RocketMQ消息中间件实战派上下册”是我既“Spring Cloud Alibaba微…

前端效果 登入界面

文章目录 效果展示&#xff1a; 代码&#xff1a; <template><div class"login"><div class"section-1"><div class"card" mouseover"activeCard 1" mouseleave"activeCard 0" click"islogin…

【SpringBoot+dubbo+zk】实现服务之间rpc通信

0)前置准备&#xff0c;我们使用zk作为注册中心&#xff0c;先启动zk&#xff0c;也就是2181端口。 1)父工程pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http…

UWB灵犀遥控器方案介绍

关键字&#xff1a;UWB技术、遥控器三维定位、遥控器指向操控 系统介绍&#xff1a;UWB灵犀遥控器&#xff0c;基于UWB TOF测距和AOA测角原理。显示器端可以同时定位遥控器的位置和朝向。实现使用遥控器指向屏幕&#xff0c;即可隔空触控&#xff0c;在大屏上实现滑动、拖拽、点…

编译原理第二次小班课

写给入门者的LLVM介绍 - 知乎 (zhihu.com) 代码优化与LLVM IR pass | Kiprey’s Blog A Tour to LLVM IR&#xff08;上&#xff09; - 知乎 (zhihu.com) 第5章 LLVM中间表示 — Getting Started with LLVM Core Libraries 文档 (getting-started-with-llvm-core-libraries-zh-…

Python从入门到网络爬虫(异常处理详解)

前言 异常即是一个事件&#xff0c;该事件会在程序执行过程中发生&#xff0c;影响了程序的正常执行。一般情况下&#xff0c;在python无法正常处理程序时就会发生一个异常。异常是python对象&#xff0c;表示一个错误。当python脚本发生异常时我们需要捕获处理它&#xff0c;…

【leetcode】力扣热门之反转链表【简单难度】

题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 用例 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1] 输入&#xff1a;head [] 输出&#xff1a;[…

软件测试|深入理解SQL FULL JOIN:语法、用法及示例解析

简介 在SQL中&#xff0c;JOIN是一个强大的操作&#xff0c;它允许将两个或多个表中的数据进行关联。SQL提供了多种JOIN类型&#xff0c;其中之一是FULL JOIN。FULL JOIN允许从左表和右表中选择所有记录&#xff0c;并将它们组合在一起。本文将深入探讨SQL FULL JOIN的语法、用…

ASP.NET Core基础之图片文件(二)-WebApi图片文件上传到文件夹

阅读本文你的收获&#xff1a; 了解WebApi项目保存上传图片的三种方式学习在WebApi项目中如何上传图片到指定文件夹中 在ASP.NET Core基础之图片文件(一)-WebApi访问静态图片文章中&#xff0c;学习了如何获取WebApi中的静态图片&#xff0c;本文继续分享如何上传图片。 那么…

单点测距传感器|激光扫描传感器SPR系列安装方法

单点测距传感器|激光扫描传感器可用于对物体进行非接触式距离测量&#xff0c;其十分广泛的应用于工业自动化、生产线、传送带等工业自动化场景中&#xff0c;也可以使用测距传感器进行物体的距离测量和位置检测、AGV和又车的碰撞保护&#xff0c;机器人工作范围的量程检测&…

C++实现单例模式

单例模式&#xff1a; 一种设计模式&#xff0c;它的目的是确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问这个实例。它适用于需要全局唯一的对象或资源的情况。 23种设计模式种最简单最常见的一种&#xff08;高频考点&#xff09; 要求&#xff1a;通过一个…

移动通信原理与关键技术学习(3)

1.什么是相干解调&#xff1f;什么是非相干解调&#xff1f;各自的优缺点是什么&#xff1f; 相干解调需要在接收端有一个与发送端一样的载波&#xff08;同样的频率和相位&#xff09;&#xff0c;在接收端的载波与发送端载波进行互相关操作&#xff0c;去除载波的影响。相干…

ssm使用web工程的相关知识

不使用框架创建web的两种方式&#xff08;这里是idea2022.3.2版&#xff09; 第一种&#xff1a;项目右键点击&#xff1a;add Framwork support选择框架进行创建。 操作步骤&#xff1a; 使用这种方式创建可能会存在的问题&#xff1a; 如果你创建web框架前&#xff1a;在…

【Flutter 开发实战】Dart 基础篇:常见的数据类型

Dart 支持许多数据类型&#xff0c;包括我们常见的 Numbers&#xff08;数值类型&#xff09;、Strings&#xff08;字符串类型&#xff09;、Booleans&#xff08;布尔类型&#xff09;&#xff0c;也支持一些包括 Collections&#xff08;集合类型&#xff09;、Records&…

嵌入式项目——平衡小车(1)

焊接 驱动板需要焊接的如上图。 陀螺仪8pin排母电机两路排线插口。(个别同学需要焊接)两个电池仓,注意电池仓分正反。 安装 底部电池板 4个 双通尼龙柱M3*224个 尼龙螺钉M3*6电机驱动板

环境搭建 之 Ubuntu 安装

ubuntu-releases-20.04.6安装包下载_开源镜像站-阿里云ubuntu-releases-20.04.6安装包是阿里云官方提供的开源镜像免费下载服务&#xff0c;每天下载量过亿&#xff0c;阿里巴巴开源镜像站为包含ubuntu-releases-20.04.6安装包的几百个操作系统镜像和依赖包镜像进行免费CDN加速…

Matplotlib for C++不完全手册

matplotlib-cpp是Matplotlib&#xff08;MPL&#xff09;为C提供的一个用于python的matplotlib绘图库的C包装器。它的构建类似于Matlab和matplotlib使用的绘图API。 However, the function signatures might differ and Matplotlib for C does not support the full functional…

【嵌入式】Makefile 学习笔记记录 | 嵌入式Linux

文章目录 前言一、Makefile的引入——最简单的gcc编译过程二、Makefile的规则三、Makefile的语法3.1、通配符3.2、假想目标 .phony3.3、即时变量 延时变量 四、Makefile的函数4.1、foreach4.2、filter4.3、wildcard4.4、patsubst 五、Makefile升级5.1、包含头文件在内的依赖关系…