什么是堆(Heap)?

堆是一种特殊的树形数据结构,它满足以下两个主要属性:

  1. 结构性(完全二叉树):

    • 堆总是一个完全二叉树 (Complete Binary Tree)。这意味着,除了最后一层,所有的层都是完全填充的,并且最后一层的所有节点都尽可能地向左填充。

    • 这个特性使得堆非常适合用数组来表示,因为节点之间的父子关系可以通过索引轻松计算。

  2. 堆序性(Heap Property):

    • 最大堆(Max-Heap): 在一个最大堆中,每个父节点的值都大于或等于其子节点的值。这意味着,根节点是整个堆中的最大元素。

    • 最小堆(Min-Heap): 在一个最小堆中,每个父节点的值都小于或等于其子节点的值。这意味着,根节点是整个堆中的最小元素。

总结: 堆是一个用数组实现的完全二叉树,并且满足特定的堆序性(父节点大于等于子节点或小于等于子节点)。

堆的ADT(抽象数据类型)操作

一个典型的堆通常支持以下操作:

  1. createHeap(capacity): 创建一个指定容量的空堆。

  2. isFull(heap): 检查堆是否已满。

  3. isEmpty(heap): 检查堆是否为空。

  4. insert(heap, value): 将新元素插入堆中,并保持堆的性质。

  5. deleteMax(heap) / deleteMin(heap): 删除堆中最大/最小元素(通常是根节点),并保持堆的性质。

  6. peekMax(heap) / peekMin(heap): 查看堆中最大/最小元素(根节点),但不删除。

  7. heapifyUp(heap, index): 向上调整堆,当子节点的值发生变化(通常是插入操作后)。

  8. heapifyDown(heap, index): 向下调整堆,当父节点的值发生变化(通常是删除操作或建堆操作后)。

  9. buildHeap(array, size): 将一个无序数组转换为一个堆。

堆的C语言实现原理(基于数组)

由于堆是完全二叉树,它最常用的实现方式是使用数组。这种实现方式的优点在于:

  • 空间效率高: 无需存储指针。

  • 计算效率高: 父子节点的关系可以通过简单的算术运算得出。

对于一个基于0索引的数组 arr

  • 根节点: arr[0]

  • 节点 i 的左子节点: arr[2 * i + 1]

  • 节点 i 的右子节点: arr[2 * i + 2]

  • 节点 i 的父节点: arr[(i - 1) / 2] (注意整数除法)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h> // For bool type// -----------------------------------------------------------
// 1. 定义堆结构体
// -----------------------------------------------------------
typedef struct MaxHeap {int* arr;       // 存储堆元素的数组int capacity;   // 堆的最大容量int size;       // 堆当前元素的数量
} MaxHeap;// -----------------------------------------------------------
// 2. 辅助函数
// -----------------------------------------------------------// 交换两个整数的值
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}// -----------------------------------------------------------
// 3. 核心堆操作函数
// -----------------------------------------------------------// 创建一个空的MaxHeap
MaxHeap* createMaxHeap(int capacity) {MaxHeap* newHeap = (MaxHeap*)malloc(sizeof(MaxHeap));if (newHeap == NULL) {perror("Failed to allocate memory for MaxHeap structure");exit(EXIT_FAILURE);}newHeap->arr = (int*)malloc(sizeof(int) * capacity);if (newHeap->arr == NULL) {perror("Failed to allocate memory for MaxHeap array");free(newHeap); // Clean up partially allocated memoryexit(EXIT_FAILURE);}newHeap->capacity = capacity;newHeap->size = 0;printf("MaxHeap created with capacity: %d\n", capacity);return newHeap;
}// 释放堆占用的内存
void destroyMaxHeap(MaxHeap* heap) {if (heap != NULL) {free(heap->arr);heap->arr = NULL;free(heap);printf("MaxHeap destroyed.\n");}
}// 检查堆是否为空
bool isEmpty(MaxHeap* heap) {return heap->size == 0;
}// 检查堆是否已满
bool isFull(MaxHeap* heap) {return heap->size == heap->capacity;
}// 获取父节点的索引
int getParentIndex(int i) {return (i - 1) / 2;
}// 获取左子节点的索引
int getLeftChildIndex(int i) {return 2 * i + 1;
}// 获取右子节点的索引
int ietRightChildIndex(int i) {return 2 * i + 2;
}// 判断给定索引是否具有左子节点
bool hasLeftChild(MaxHeap* heap, int i) {return getLeftChildIndex(i) < heap->size;
}// 判断给定索引是否具有右子节点
bool hasRightChild(MaxHeap* heap, int i) {return ietRightChildIndex(i) < heap->size;
}/*** @brief 向上调整堆 (Heapify Up)* 当子节点的值大于父节点时,将其与父节点交换,直到满足堆性质或到达根节点。* 通常在插入新元素后调用。** @param heap 指向MaxHeap的指针* @param index 待调整元素的当前索引*/
void heapifyUp(MaxHeap* heap, int index) {// 当当前节点不是根节点,且当前节点的值大于其父节点的值时while (index > 0 && heap->arr[getParentIndex(index)] < heap->arr[index]) {swap(&heap->arr[index], &heap->arr[getParentIndex(index)]);index = getParentIndex(index); // 移动到父节点的位置,继续向上检查}
}/*** @brief 向下调整堆 (Heapify Down)* 当父节点的值小于其子节点时,将其与最大的子节点交换,直到满足堆性质或到达叶子节点。* 通常在删除根元素或建堆时调用。** @param heap 指向MaxHeap的指针* @param index 待调整元素的当前索引*/
void heapifyDown(MaxHeap* heap, int index) {int maxIndex = index;int leftChild = getLeftChildIndex(index);int rightChild = ietRightChildIndex(index);// 检查左子节点是否存在且大于当前最大值if (hasLeftChild(heap, index) && heap->arr[leftChild] > heap->arr[maxIndex]) {maxIndex = leftChild;}// 检查右子节点是否存在且大于当前最大值if (hasRightChild(heap, index) && heap->arr[rightChild] > heap->arr[maxIndex]) {maxIndex = rightChild;}// 如果maxIndex不是当前index,说明子节点比父节点大,需要交换if (maxIndex != index) {swap(&heap->arr[index], &heap->arr[maxIndex]);heapifyDown(heap, maxIndex); // 递归向下调整}
}/*** @brief 插入一个元素到最大堆* 将新元素放到数组末尾,然后向上调整以维护堆性质。** @param heap 指向MaxHeap的指针* @param value 要插入的值* @return true 插入成功* @return false 堆已满,插入失败*/
bool insert(MaxHeap* heap, int value) {if (isFull(heap)) {printf("Error: Heap is full. Cannot insert %d.\n", value);return false;}heap->arr[heap->size] = value; // 将新元素放到数组末尾heap->size++;                 // 堆大小加1heapifyUp(heap, heap->size - 1); // 从新元素位置向上调整printf("Inserted: %d\n", value);return true;
}/*** @brief 从最大堆中删除最大元素(根元素)* 将根元素与最后一个元素交换,然后删除最后一个元素,最后向下调整新的根元素。** @param heap 指向MaxHeap的指针* @param deletedValue 用于存储被删除的值的指针* @return true 删除成功* @return false 堆为空,删除失败*/
bool deleteMax(MaxHeap* heap, int* deletedValue) {if (isEmpty(heap)) {printf("Error: Heap is empty. Cannot delete.\n");return false;}*deletedValue = heap->arr[0]; // 存储根元素的值heap->arr[0] = heap->arr[heap->size - 1]; // 将最后一个元素移动到根heap->size--;                            // 堆大小减1heapifyDown(heap, 0);                    // 从根节点向下调整printf("Deleted Max: %d\n", *deletedValue);return true;
}/*** @brief 查看最大堆的根元素(最大值)** @param heap 指向MaxHeap的指针* @param peekedValue 用于存储查看到的值的指针* @return true 查看成功* @return false 堆为空,查看失败*/
bool peekMax(MaxHeap* heap, int* peekedValue) {if (isEmpty(heap)) {printf("Error: Heap is empty. No max element.\n");return false;}*peekedValue = heap->arr[0];printf("Peeked Max: %d\n", *peekedValue);return true;
}/*** @brief 打印堆的当前元素(按数组顺序)* 注意:这只是打印数组内容,不代表堆的树形结构。*/
void printHeap(MaxHeap* heap) {printf("Heap elements (array order): [");for (int i = 0; i < heap->size; i++) {printf("%d", heap->arr[i]);if (i < heap->size - 1) {printf(", ");}}printf("] (Size: %d, Capacity: %d)\n", heap->size, heap->capacity);
}/*** @brief 将一个无序数组构建成一个最大堆* 从最后一个非叶子节点开始,依次向上调整每个节点。* 时间复杂度:O(n)** @param heap 指向MaxHeap的指针* @param arr 待构建的原始数组* @param size 原始数组的大小* @return true 成功构建* @return false 原始数组大小超过堆容量*/
bool buildHeap(MaxHeap* heap, int* arr, int size) {if (size > heap->capacity) {printf("Error: Array size (%d) exceeds heap capacity (%d).\n", size, heap->capacity);return false;}for (int i = 0; i < size; i++) {heap->arr[i] = arr[i];}heap->size = size;// 从最后一个非叶子节点开始向上调整// 最后一个非叶子节点的索引是 (size / 2) - 1for (int i = (heap->size / 2) - 1; i >= 0; i--) {heapifyDown(heap, i);}printf("Heap built from array. \n");return true;
}// -----------------------------------------------------------
// 4. 主函数进行测试
// -----------------------------------------------------------
int main() {MaxHeap* myHeap = createMaxHeap(10); // 创建一个容量为10的堆// 插入操作测试insert(myHeap, 30);insert(myHeap, 10);insert(myHeap, 50);insert(myHeap, 20);insert(myHeap, 40);printHeap(myHeap); // 期望: [50, 40, 30, 10, 20] (或其他有效堆排列)// 查看最大值测试int maxVal;peekMax(myHeap, &maxVal); // 期望: 50// 删除操作测试deleteMax(myHeap, &maxVal); // 期望: 删除 50printHeap(myHeap); // 期望: [40, 20, 30, 10] (或其他有效堆排列)deleteMax(myHeap, &maxVal); // 期望: 删除 40printHeap(myHeap); // 期望: [30, 20, 10] (或其他有效堆排列)// 插入更多元素直到满insert(myHeap, 60);insert(myHeap, 70);insert(myHeap, 5);insert(myHeap, 90);insert(myHeap, 80);insert(myHeap, 100);insert(myHeap, 15);printHeap(myHeap); // 期望: 堆满,10个元素// 尝试插入超出容量insert(myHeap, 101); // 期望: 插入失败提示// 从数组建堆测试MaxHeap* anotherHeap = createMaxHeap(8);int arr[] = {3, 9, 2, 8, 1, 6, 7, 5};int n = sizeof(arr) / sizeof(arr[0]);printf("\nBuilding heap from array: ");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");buildHeap(anotherHeap, arr, n);printHeap(anotherHeap); // 期望: [9, 8, 7, 5, 1, 6, 2, 3] (或其他有效堆排列)deleteMax(anotherHeap, &maxVal);printHeap(anotherHeap);// 释放内存destroyMaxHeap(myHeap);destroyMaxHeap(anotherHeap);return 0;
}

代码解释:

  1. MaxHeap 结构体:

    • arr: 指向存储堆元素的动态数组。

    • capacity: 数组的最大容量。

    • size: 堆中当前元素的数量,也是下一个要插入元素的索引。

  2. 辅助函数:

    • swap(): 简单的值交换函数。

    • getParentIndex()getLeftChildIndex()ietRightChildIndex(): 根据数组索引计算父子节点索引。

    • hasLeftChild()hasRightChild(): 判断一个节点是否有对应的子节点,防止越界。

    • isEmpty()isFull(): 检查堆的状态。

  3. 核心堆操作:

    • createMaxHeap() / destroyMaxHeap(): 堆的内存分配与释放。

    • heapifyUp(index) (向上调整):

      • 当新元素插入到堆的末尾时,它可能比其父节点大,违反了最大堆性质。

      • heapifyUp 将该元素与其父节点比较,如果大于父节点则交换位置。

      • 这个过程持续向上,直到元素回到正确位置(不大于父节点)或到达根节点。

      • 时间复杂度: O(log N),N 是堆的大小(因为每次操作向上移动一层)。

    • heapifyDown(index) (向下调整):

      • 当删除根节点(最大元素)后,或在建堆过程中,根节点被替换为最后一个元素,可能比其子节点小。

      • heapifyDown 将当前节点与其左右子节点比较,找出最大的子节点。

      • 如果当前节点小于最大的子节点,则与最大的子节点交换位置,然后对交换后的子节点位置递归调用 heapifyDown

      • 这个过程持续向下,直到元素回到正确位置(不小于任何子节点)或到达叶子节点。

      • 时间复杂度: O(log N)。

    • insert(value):

      • 将新元素添加到数组的末尾(heap->arr[heap->size])。

      • heap->size 增加。

      • 调用 heapifyUp 从新元素的位置向上调整,恢复堆性质。

      • 时间复杂度: O(log N)。

    • deleteMax(deletedValue):

      • 如果堆为空,返回失败。

      • 保存根节点的值(heap->arr[0])作为返回值。

      • 将数组的最后一个元素移动到根节点位置(heap->arr[0] = heap->arr[heap->size - 1])。

      • heap->size 减少。

      • 调用 heapifyDown 从根节点位置向下调整,恢复堆性质。

      • 时间复杂度: O(log N)。

    • peekMax(): 返回根元素的值,不删除。

    • buildHeap(arr, size) (建堆):

      • 将给定数组的元素直接复制到堆的内部数组中。

      • 最后一个非叶子节点开始(索引为 (size / 2) - 1),向上遍历到根节点。

      • 对每个非叶子节点调用 heapifyDown,确保其子树满足堆性质。

      • 时间复杂度: O(N)。这比 N 次 insert 操作(O(N log N))更高效。

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

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

相关文章

【前后前】导入Excel文件闭环模型:Vue3前端上传Excel文件,【Java后端接收、解析、返回数据】,Vue3前端接收展示数据

【前后前】导入Excel文件闭环模型&#xff1a;Vue3前端上传Excel文件&#xff0c;【Java后端接收、解析、返回数据】&#xff0c;Vue3前端接收展示数据 一、Vue3前端上传&#xff08;导入&#xff09;Excel文件 ReagentInDialog.vue <script setup lang"ts" na…

网络基础入门:从OSI模型到TCP/IP协议详解

网络基础入门&#xff1a;从OSI模型到TCP/IP协议详解 一、网络基础概念与OSI七层模型 1.1 网络通信的本质 计算机网络的核心是将抽象语言转换为二进制数据进行传输与计算&#xff0c;这一过程涉及多层抽象与转换&#xff1a; 应用层&#xff1a;人机交互—抽象语言------编…

Linux致命漏洞CVE-2025-6018和CVE-2025-6019

Qualys 最近披露了两个影响主流 Linux 发行版的本地权限提升 (LPE) 漏洞&#xff0c;分别是 CVE-2025-6018 和 CVE-2025-6019。这两个漏洞可以被串联利用&#xff0c;使得非特权用户在几秒钟内获得系统的 root 权限&#xff0c;从而实现对系统的完全控制。 一、漏洞详情 这两…

【Docker基础】Docker镜像管理:docker push详解

目录 引言 1 Docker镜像推送基础概念 1.1 什么是Docker镜像推送 1.2 镜像仓库概述 1.3 镜像标签与版本控制 2 docker push命令详解 2.1 基本语法 2.2 常用参数选项 2.3 实际命令示例 2.4 推送流程 2.5 步骤描述 3 镜像推送实践示例 3.1 登录管理 3.2 标签管理 3…

FPGA基础 -- Verilog行为建模之循环语句

行为级建模&#xff08;Behavioral Modeling&#xff09;是 Verilog HDL 中最接近软件编程语言的一种描述方式&#xff0c;适用于功能建模和仿真建模的初期阶段。在行为级中&#xff0c;循环语句&#xff08;loop statements&#xff09;是常见且重要的控制结构&#xff0c;用于…

从C学C++(7)——static成员

从C学C(7)——static成员 若无特殊说明&#xff0c;本博客所执行的C标准均为C11. static成员和成员函数 对于特定类型的全体对象而言&#xff0c;有时候可能需要访问一个全局的变量。比如说统计某种类型对象已创建的数量。 通常在C中使用全局变量来实现&#xff0c;如果我们…

大模型和ollama一起打包到一个docker镜像中

如何将大模型镜像和 Ollama 镜像打包在一个 Docker 镜像中 最近工作中有个需求是将ollama和大模型一起打成一个镜像部署&#xff0c;将自己的操作步骤分享给大家。将大模型与 Ollama 服务打包在同一个 Docker 镜像中&#xff0c;可以简化部署流程并确保环境一致性。下面详细介…

2025年渗透测试面试题总结-攻防研究员(应用安全)(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 攻防研究员(应用安全) 一、基础部分 1. HTTP状态码对比 2. HTTP请求方法核心作用 3. 网络分层协议速查表…

SpringBoot新闻项目学习day3--后台权限的增删改查以及权限管理分配

新增管理员修改管理员删除管理员登录 新增管理员 1.点击新增按钮打开一个对话框 2.确定新增对话框要显示哪些内容 3.提交 4.后端处理、保存 5.响应前端 vue代码 <template><!-- 新增代码内容是比较多的,建议抽取出来,定义到一个独立的vue文件中在列表组件中导入…

算法导论第二十五章 深度学习的伦理与社会影响

第二十五章 深度学习的伦理与社会影响 技术的光芒不应掩盖伦理的阴影 随着深度学习技术在各领域的广泛应用&#xff0c;其引发的伦理和社会问题日益凸显。本章将深入探讨这些挑战&#xff0c;并提供技术解决方案和最佳实践&#xff0c;引导读者构建负责任的人工智能系统。 25.…

Linux中ansible模块补充和playbook讲解

一、模块使用 1.1 Yum模块 功能&#xff1a;管理软件包&#xff0c;只支持RHEL&#xff0c;CentOS&#xff0c;fedora&#xff0c;不支持Ubuntu其它版本 参数说明name要操作的软件包名称&#xff0c;支持通配符&#xff08;如 httpd, nginx*&#xff09;&#xff0c;也可以是…

唐代大模型:智能重构下的盛世文明图谱

引言&#xff1a;当长安城遇见深度学习 一件唐代鎏金舞马衔杯银壶的虚拟复原品正通过全息投影技术演绎盛唐乐舞。这个跨越时空的场景&#xff0c;恰似唐代大模型技术的隐喻——以人工智能为纽带&#xff0c;连接起长安城的盛世气象与数字时代的文明重构。作为人工智能与历史学…

国产ARM/RISCV与OpenHarmony物联网项目(三)网关设备控制

一、设备控制界面与功能设计 程序界面运行与设计效果如下: 设备控制相关程序调用关系图如下&#xff1a; 其中device_control.html程序为网页界面显示程序&#xff0c;led_alarm.cgi程序为光线数据的报警超限数据设置与管理&#xff0c;led_control.cgi程序功能为对Led灯的开…

微信小程序反编译实战教程

在实际渗透测试或安全分析中&#xff0c;经常会遇到微信小程序中的签名加密&#xff08;sign&#xff09;机制&#xff0c;这些机制大多具备防重放、防篡改的特性&#xff0c;导致我们在抓包时难以直接复现请求。 &#x1f50d; 另一方面&#xff0c;一些小程序的代码中往往会…

【NLP入门系列三】NLP文本嵌入(以Embedding和EmbeddingBag为例)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒…

文心一言(ERNIE Bot):百度打造的知识增强大语言模型

1. 产品概述 文心一言&#xff08;ERNIE Bot&#xff09;是百度自主研发的知识增强大语言模型&#xff0c;于2023年3月16日正式发布&#xff0c;对标OpenAI的ChatGPT&#xff0c;具备文本生成、多模态交互、逻辑推理、中文理解等能力。该模型基于百度的飞桨深度学习平台和文心…

Java-49 深入浅出 Tomcat 手写 Tomcat 实现【02】HttpServlet Request RequestProcessor

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月13日更新到&#xff1a; AI炼丹日志-28 - Aud…

在VB.net中,文本插入的几个自定义函数

一、如果你是高手&#xff0c;一定“识货”&#xff0c;分享给你 二、可应用于文本插入的几种方式&#xff1a;6种 三、需要用到以下的几个函数&#xff1a; 上代码&#xff1a; Module TextModule <summary> 在指定位置插入文本 </summary> <p…

QC -io 服务器排查报错方式/报错: Failed to convert string to integer of varId variable!“

进断点控制台有报错之后&#xff0c;复制报错信息到 头部菜单栏 1.编辑 -> 2.Find/Replace ->3.Advanced Find ->4. Project“xxxxx” 能找到问题点 再分析定位 在排查报错时候&#xff0c;进入了这个报错&#xff0c;msgInfo "MyTcpRedis: Failed to conver…

c++中auto与decltype使用

在 C11及后续版本中&#xff0c;关键字auto和decltype都是用于类型推导的&#xff0c;但它们的使用场景和行为有所不同。 1. auto 关键字 作用 auto 用于自动推导变量的类型&#xff0c;由编译器根据初始化表达式来确定。 常见用法 // 基本用法 auto x 42; // int…