【数据结构 · 初阶】- 堆的实现

目录

一.初始化

二.插入

 三.删除(堆顶、根)

四.整体代码

Heap.h

Test.c

Heap.c


我们使用顺序结构实现完全二叉树,也就是堆的实现

以前学的数据结构只是单纯的存储数据。堆除了存储数据,还有其他的价值——排序。是一个功能性的数据结构

小根堆堆顶的数据一定是最小的,大根堆堆顶的数据一定是最大的
选出最大/最小,再选次大/次小……不断选最后就帮助排序。还可以解决取前几,后几的TOP-K 问题 

我们以建大堆为例。建小堆只需改变 爸 < 娃 即可


一.初始化

下面多次用到交换,将交换分装成函数

Heap.h

typedef int HPDataTypt;typedef struct Heap
{HPDataTypt* a; // 数组指针,指向要开辟的存储数据的数组int size; // 当前已存储的有效数据个数int capacity; // 最大容量
}HP;void HeapInit(HP* php); // 初始化
void HeapDestroy(HP* php); // 销毁

Heap.c 

void HeapInit(HP* php)
{assert(php);php->a = (HPDataTypt*)malloc(sizeof(HPDataTypt) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}void Swap(HPDataTypt* p1, HPDataTypt* p2)
{HPDataTypt tmp = *p1;*p1 = *p2;*p2 = tmp;
}

php 是指向主函数中,HP(结构体类型)的变量 hp 地址的指针。php 中存放的是 hp 的地址。若为空就说明结构体没有开好,所以一定不能为空,断言。

二.插入

堆的底层就是数组,可以插入数据。要把控制数组想象成控制树。原来是大根堆,插入后,要求还得是堆。
插入前是堆,插入后会影响部分祖先(跟祖先调整)

以大根堆为例,看最简单的情况:插入20,插入后不影响堆的性质。

    

再插入60,插入后要调整。 

为保证父亲 > 娃,要交换   

娃 还> 父亲,继续交换 父亲 > 娃,结束

上面的过程叫 向上调整 ,最多调整高度次,时间复杂度:O( log N )。插入一个数据,想让他再调整成堆只要 log N 次


堆的插入不像链表、顺序表,不能想往哪插就往哪插,要保持性质。

上面的尾插,如果堆原来是这样,就不能尾插20 

所以插入单纯的叫 Push 就好,因为不是由接口指定在哪个位置插入。


void AdjustUp(HPDataTypt* a, int 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; // 娃 <= 爸,不往上走}}
}void HeapPush(HP* php, HPDataTypt x)
{assert(php);if (php->size == php->capacity){HPDataTypt* tmp = (HPDataTypt*)realloc(php->a, sizeof(HPDataTypt) * php->capacity * 2);if (tmp == NULL){perror("malloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1); // 从孩子(插入数据)位置向上调整
}

 三.删除(堆顶、根)

堆删除,删尾轻松,但无意义。

为什么删堆顶、根才有意义?老大被干掉了,老二才能冒头。

堆实现的意义,无论是排序还是 top-k ,本质是在帮我选数,选出最大/最小数

删除后,也要保证是堆。要把最大的删掉,怎么搞?


不能挪动删除(直接删)!原因:1.效率低下   2.父子兄弟关系全乱了


正确方法:(间接删) 堆顶和最后的元素换一下;--size,使换下去的最后一个(原堆顶)元素失效
1.效率高   2.最大程度的保持了父子关系

单看左右子树依旧是大堆,换上去的原最后元素大概率是比较小的,就要向下调整


看下面的新场景:为保证换了之后父亲 > 娃,现在的堆顶(原最后一个元素)要跟大的娃换。

娃中大的 > 爸,把爸换下去 继续换     

最坏情况调到叶子结束。物理上是数组,怎么判断到叶子——没有娃,怎么判断没有娃呢?
把它当做爸,算左娃的下标,如果超出数组范围就没娃,所以参数要多给个数组的大小 size,用来判断 child 是否越界

最坏走高度 log N 次

void AdjustDown(HPDataTypt* a, int n, int parent)
{int child = parent * 2 + 1; // 默认左孩子大,将左孩子定为 childwhile (child < n){// 选出左右孩子中大的那一个if (child + 1 < n && a[child] < a[child + 1]) // 防止无右娃的越界风险{child++; // 如果右孩子大,++后,child 就是右孩子}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));Swap(&php->a[0], &php->a[php->size - 1]); // 交换堆顶、最后元素php->size--; // 删除换下来的原堆顶元素AdjustDown(php->a, php->size, 0); // 向下调整,0是开始调整位置的下标// n 是有效数据个数,作为下标,用来判断 child 是否越界
}

向上调整的前提:除了 child 这个位置,前面的数据构成堆
向下调整的前提:保证左右子树都是堆

四.整体代码

Heap.h

typedef int HPDataTypt;typedef struct Heap
{HPDataTypt* a; // 数组指针,指向要开辟的存储数据的数组int size; // 当前已存储的有效数据个数int capacity; // 最大容量
}HP;void HeapInit(HP* php); // 初始化
void HeapDestroy(HP* php); // 销毁void HeapPush(HP* php, HPDataTypt x); // 插入
void HeapPop(HP* php);// 删除堆顶HPDataTypt HeapTop(HP* php); // 堆顶的数据
bool HeapEmpty(HP* php); // 探空
int HeapSize(HP* php);void AdjustUp(HPDataTypt* a, int child); // 向上调整
void AdjustDown(HPDataTypt* a, int n, int parent); // 向下调整

Test.c

void test1() // 排序
{HP hp;HeapInit(&hp);HeapPush(&hp, 2);HeapPush(&hp, 45);HeapPush(&hp, 76);HeapPush(&hp, 23);HeapPush(&hp, 5654);HeapPush(&hp, 24);HeapPush(&hp, 5);HeapPush(&hp, 242);HeapPush(&hp, 25);while (!HeapEmpty(&hp)){printf("%d ", HeapTop(&hp));HeapPop(&hp);// 选老二,必须干掉老大}HeapDestroy(&hp);
}void test2() // top-k
{HP hp;HeapInit(&hp);HeapPush(&hp, 2);HeapPush(&hp, 45);HeapPush(&hp, 76);HeapPush(&hp, 23);HeapPush(&hp, 5654);HeapPush(&hp, 24);HeapPush(&hp, 5);HeapPush(&hp, 242);HeapPush(&hp, 25);HeapPush(&hp, 5);HeapPush(&hp, 5);int k = 0;scanf("%d", &k);while (!HeapEmpty(&hp) && k--){printf("%d ", HeapTop(&hp));HeapPop(&hp);// 选老二,必须干掉老大}HeapDestroy(&hp);
}

Heap.c

void HeapInit(HP* php)
{assert(php);php->a = (HPDataTypt*)malloc(sizeof(HPDataTypt) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}void Swap(HPDataTypt* p1, HPDataTypt* p2)
{HPDataTypt tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDataTypt* a, int 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; // 娃 <= 爸,不往上走}}
}void HeapPush(HP* php, HPDataTypt x)
{assert(php);if (php->size == php->capacity){HPDataTypt* tmp = (HPDataTypt*)realloc(php->a, sizeof(HPDataTypt) * php->capacity * 2);if (tmp == NULL){perror("malloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1); // 从孩子(插入数据)位置向上调整
}void AdjustDown(HPDataTypt* a, int n, int parent)
{int child = parent * 2 + 1; // 默认左孩子大,将左孩子定为 childwhile (child < n){// 选出左右孩子中大的那一个if (child + 1 < n && a[child] < a[child + 1]) // 防止无右娃的越界风险{child++; // 如果右孩子大,++后,child 就是右孩子}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));Swap(&php->a[0], &php->a[php->size - 1]); // 交换堆顶、最后元素php->size--; // 删除换下来的原堆顶元素AdjustDown(php->a, php->size, 0); // 向下调整,0是开始调整位置的下标// n 是有效数据个数,作为下标,用来判断 child 是否越界
}HPDataTypt HeapTop(HP* php)
{assert(php);return php->a[0];
}bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}int HeapSize(HP* php)
{assert(php);return php->size;
}void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

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

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

相关文章

qt.tlsbackend.ossl: Failed to load libssl/libcrypto.

我的环境是windows&#xff0c;QT6.3.2&#xff08;msvc2019_64/mingw_64&#xff09; 出错原因 QT没有正确加载OpenSSL。 解决过程 1、确保安装的有openssl。 文章结尾有个注意&#xff0c;是其他方式安装过openssl&#xff0c;环境变量有&#xff0c;但是QT找不到的问题。…

【Linux】用户权限

shell命令 1. Linux本质上是一个操作系统&#xff0c;但是一般的用户不能直接使用它&#xff0c;而是需要通过外壳程序shell&#xff0c;来与Linux内核进行沟通。 2. shell的简单定义&#xff1a;命令行解释器。主要包含以下作用&#xff1a; 将使用者的命令翻译给核心处理。将…

赛灵思 XC7K325T-2FFG900I FPGA Xilinx Kintex‑7

XC7K325T-2FFG900I 是 Xilinx Kintex‑7 系列中一款工业级 (I) 高性能 FPGA&#xff0c;基于 28 nm HKMG HPL 工艺制程&#xff0c;核心电压标称 1.0 V&#xff0c;I/O 电压可在 0.97 V–1.03 V 之间灵活配置&#xff0c;并可在 –40 C 至 100 C 温度范围内稳定运行。该器件提供…

【题解-Acwing】847. 图中点的层次

题目:847. 图中点的层次 题目描述 给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环。 所有边的长度都是 1,点的编号为 1∼n。 请你求出 1 号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 −1 。 输入 第一行包含两个整数 n 和 m。 接下来 m 行…

css图片设为灰色

使用filter方式将图片设置为灰色 普通图片使用&#xff1a;filter: saturate(0); 纯白图片使用&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"width…

【Luogu】动态规划一

P5414 [YNOI2019] 排序 - 洛谷 思路&#xff1a; 可以想到对于任意一个需要换位置的数字&#xff0c;我们不可能换两次及以上&#xff0c;那么这题就可以转化为求一个最大和的最长不递减子序列&#xff0c;最后的答案就是众和减去这个最大和 代码&#xff1a; #include <…

什么是管理思维?

管理思维是指在管理活动中形成的系统性、战略性和创造性的思考方式&#xff0c;帮助个人或团队更高效地达成目标。它不仅适用于企业管理&#xff0c;也适用于个人成长、项目执行和复杂问题解决。以下是关于管理思维的核心内容&#xff1a; 一、管理思维的核心特征 1. 系统性思…

利用TCP+多进程技术实现私聊信息

服务器&#xff1a; import socket from multiprocessing import Process from threading import Threaduser_dic {}def send_recv(client_conn, client_addr):while 1:# 接收客户端发送的消息res client_conn.recv(1024).decode("utf-8")print("客户端发送…

Hbuilder 上的水印相机实现方案 (vue3 + vite + hbuilder)

效果 思路 通过 live-pusher 这个视频推流的组件来获取摄像头拿到视频的一帧图片之后&#xff0c;跳转到正常的 vue 页面&#xff0c;通过 canvas 来处理图片水印 源码 live-pusher 这个组件必须是 nvue 的 至于什么是 nvue&#xff0c;看这个官方文档吧 https://uniapp.dcl…

Spark,IDEA编写Maven项目

IDEA中编写Maven项目 1.打开IDEA新建项目2.选择java语言&#xff0c;构建系统选择Maven 3.IDEA中配置Maven 注&#xff1a;这些文件都是我们老师帮我们在网上找了改动后给我们的&#xff0c;大家可自行在网上查找 编写代码测试HDFS连接 1.在之前创建的pom.xml文件中添加下…

初识Redis · C++客户端set和zset

目录 前言&#xff1a; set sadd sismember smembers spop scard sinter sinterstore zset zadd zrange zcard zrem zrank zscore 前言&#xff1a; 前文我们已经介绍了string list hash在Redis-plus-plus的使用&#xff0c;本文我们开始介绍set和zset在redis-plus-pl…

sed命令笔记250419

sed命令笔记250419 sed&#xff08;Stream Editor&#xff09;是 Linux/Unix 系统中强大的流编辑器&#xff0c;主要用于对文本进行过滤和转换&#xff08;按行处理&#xff09;。它支持正则表达式&#xff0c;适合处理文本替换、删除、插入等操作。以下是 sed 的详细解析&…

ubuntu-24.04.2-live-server-arm64基于cloud-init实现分区自动扩容(LVM分区模式)

1. 环境 虚拟机镜像ISO&#xff1a;ubuntu-24.04.2-live-server-arm64.iso 2. 定制cloud-init镜像 2.1 安装OS 基于ubuntu-24.04.2-live-server-arm64.iso&#xff0c;通过virt-manager安装操作系统&#xff0c;语言建议选择英文&#xff0c;分区选择基于LVM的自动分区&…

vue3专题1------父组件中更改子组件的属性

理解 Vue 3 中父组件如何引用子组件的属性是一个很重要的概念。 这里涉及到 defineExpose 和 ref 这两个关键点。 方法&#xff1a;使用 defineExpose 在子组件中暴露属性&#xff0c;然后在父组件中使用 ref 获取子组件实例并访问暴露的属性。 下面我将详细解释这个过程&…

数据仓库分层架构解析:从理论到实战的完整指南​​

数据仓库分层是构建高效数据体系的核心方法论。本文系统阐述ODS、DWD、DWS、ADS四层架构的设计原理&#xff0c;结合电商用户行为分析场景&#xff0c;详解各层功能及协作流程&#xff0c;并给出分层设计的原则与避坑指南&#xff0c;帮助读者掌握分层架构的落地方法。 一、为什…

从零搭建一套前端开发环境

一、基础环境搭建 1.NVM(Node Version Manager)安装 简介 nvm&#xff08;Node Version Manager&#xff09; 是一个用于管理多个 Node.js 版本的工具&#xff0c;允许开发者在同一台机器上轻松安装、切换和使用不同版本的 Node.js。它特别适合需要同时维护多个项目&#xff…

计算机组成原理笔记(十六)——4.1基本算术运算的实现

计算机中最基本的算术运算是加法运算&#xff0c;加、减、乘、除运算最终都可以归结为加法运算。 4.1.1加法器 一、加法器的基本单元 加法器的核心单元是 全加器&#xff08;Full Adder, FA&#xff09;&#xff0c;而所有加法器都由 半加器&#xff08;Half Adder, HA&…

利用Qt创建一个模拟问答系统

界面&#xff1a; 添加了聊天显示区域&#xff08;QTextEdit&#xff09; 添加了发送按钮和清空对话按钮 优化了布局和窗口大小添加了时间戳显示 2、功能&#xff1a; 支持实时对话可以清空对话历史 支持按回车发送消息 添加了简单的关键词匹配响应系统 交互体验&#x…

神经光子渲染:物理级真实感图像生成——从麦克斯韦方程到深度学习

一、技术背景与核心突破 2025年&#xff0c;神经光子渲染&#xff08;Photonic Neural Rendering, PNR&#xff09;技术通过物理光学方程与神经辐射场的深度融合&#xff0c;在AIGC检测工具&#xff08;如GPTDetector 5.0&#xff09;的识别准确率从98%降至12%。该技术突破性地…

Linux中手动安装7-Zip软件文档

7zip位于EPEL源中&#xff0c;如果服务器可以联网或者配置了本地EPEL源则可以直接安装 yum install p7zip p7zip-plugins -y对于无法联网且没有配置本地EPEL源的服务器&#xff0c;可以通过官网下载安装包后&#xff0c;上传至服务器&#xff0c;手动安装 ## 下载地址&#x…