C语言 数据结构 【堆】动态模拟实现,堆排序,TOP-K问题
引言
堆的各个接口的实现(以代码注释为主),实现堆排序,解决经典问题:TOP-K问题
一、堆的概念与结构
堆 具有以下性质
• 堆中某个结点的值总是不大于或不小于其父结点的值;
• 堆总是一棵完全二叉树。

二、堆的模拟实现
堆底层结构用数组来实现
注意:跟结点的下标为0
分三个文件来写:
Heap.h //实现堆需要的头文件,函数的声明
Heap.c //实现堆的各个接口的代码
test.c //测式堆接口的代码
1.在Heap.h中定义堆的结构和声明要实现的函数:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//堆的结构
typedef int HPDataType;
typedef struct Heap
{HPDataType* arr; //数组int size; //有效元素个数int capacity; //数组的容量
}HP;void HPInit(HP* php); //初始化堆void Swap(int* x, int* y); //交换两个元素void HPDestroy(HP* php); //堆的销毁void HPPrint(HP* php); //打印堆数组void HPPush(HP* php, HPDataType x); //往堆中插入元素
void HPPop(HP* php); //删除堆顶元素
//取堆顶元素HPDataType HPTop(HP* php);//判空
bool HPEmpty(HP* php);//向下调整算法,时间复杂度:O(n)
void AdjustDown(HPDataType* arr, int parent, int n);//向上调整算法,时间复杂度:O(n*logn)
void AdjustUp(HPDataType* arr, int child);
2.堆的初始化 和 堆的销毁
//初始化堆
void HPInit(HP* php)
{php->arr = NULL; //先将数组的地址指向NULLphp->size = php->capacity = 0;
}
//堆的销毁
void HPDestroy(HP* php)
{if (php->arr)free(php->arr); //将数组给释放掉php->arr = NULL;php->size = php->capacity = 0;
}
3.向上调整算法和在堆中插入元素
void Swap(int* x, int* y) //实现两个元素的交换
{int tmp = *x;*x = *y;*y = tmp;
}
//向上调整算法,时间复杂度:O(n*logn)
//从插入的叶子结点向上调整
void AdjustUp(HPDataType* arr, int child)
{int parent = (child - 1) / 2; //找到父节点while (child > 0){//大堆:>//小堆:<if (arr[child] > arr[parent]) //这里创建大根堆{Swap(&arr[child], &arr[parent]); //孩子结点大于父亲结点,不符合大根堆,要交换child = parent; //将父节点设置为孩子结点,重复上面的步骤parent = (child - 1) / 2; }else{break;}}
}
void HPPush(HP* php, HPDataType x)
{assert(php);//判断空间是否足够if (php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->arr, newCapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}php->arr = tmp;php->capacity = newCapacity;}//插入新结点php->arr[php->size] = x;AdjustUp(php->arr, php->size);//将插入的结点调整成符合堆的位置++php->size; //有效元素加1
}
4.打印堆元素和判空
void HPPrint(HP* php)
{for (int i = 0; i < php->size; i++){printf("%d ", php->arr[i]);}printf("\n");
}//判空
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}
5.向下调整算法 和 删除堆顶元素
//向下调整算法,时间复杂度:O(n)
void AdjustDown(HPDataType* arr, int parent, int n)
{int child = parent * 2 + 1; //左孩子while (child < n){//大堆:<//小堆:>if (child + 1 < n && arr[child] < arr[child + 1]) child++;//大堆:>//小堆:<if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1; //还是左孩子}else{break;}}
}void HPPop(HP* php)
{assert(!HPEmpty(php));//0 php->size - 1Swap(&php->arr[0], &php->arr[php->size - 1]); //交换根节点和最后一个结点php->size--;AdjustDown(php->arr, 0, php->size);
}
6.取堆顶元素
//取堆顶数据
HPDataType HPTop(HP* php)
{assert(!HPEmpty(php));return php->arr[0];
}
三、所以代码:
1.Heap.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//堆的结构
typedef int HPDataType;
typedef struct Heap
{HPDataType* arr; //数组int size; //有效元素个数int capacity; //数组的容量
}HP;void HPInit(HP* php); //初始化堆void Swap(int* x, int* y); //交换两个元素void HPDestroy(HP* php); //堆的销毁void HPPrint(HP* php); //打印堆数组void HPPush(HP* php, HPDataType x); //往堆中插入元素
void HPPop(HP* php); //删除堆顶元素
//取堆顶元素HPDataType HPTop(HP* php);//判空
bool HPEmpty(HP* php);//向下调整算法,时间复杂度:O(n)
void AdjustDown(HPDataType* arr, int parent, int n);//向上调整算法,时间复杂度:O(n*logn)
void AdjustUp(HPDataType* arr, int child);
2.Heap.c
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"//初始化堆
void HPInit(HP* php)
{php->arr = NULL; //先将数组的地址指向NULLphp->size = php->capacity = 0;
}
//堆的销毁
void HPDestroy(HP* php)
{if (php->arr)free(php->arr); //将数组给释放掉php->arr = NULL;php->size = php->capacity = 0;
}void HPPrint(HP* php)
{for (int i = 0; i < php->size; i++){printf("%d ", php->arr[i]);}printf("\n");
}
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
//向上调整算法,时间复杂度:O(n*logn)
//从插入的叶子结点向上调整
void AdjustUp(HPDataType* arr, int child)
{int parent = (child - 1) / 2; //找到父节点while (child > 0){//大堆:>//小堆:<if (arr[child] > arr[parent]) //这里创建大根堆{Swap(&arr[child], &arr[parent]); //孩子结点大于父亲结点,不符合大根堆,要交换child = parent; //将父节点设置为孩子结点,重复上面的步骤parent = (child - 1) / 2; }else{break;}}
}
void HPPush(HP* php, HPDataType x)
{assert(php);//判断空间是否足够if (php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->arr, newCapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}php->arr = tmp;php->capacity = newCapacity;}//插入新结点php->arr[php->size] = x;AdjustUp(php->arr, php->size);//将插入的结点调整成符合堆的位置++php->size; //有效元素加1
}
//判空
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}//向下调整算法,时间复杂度:O(n)
void AdjustDown(HPDataType* arr, int parent, int n)
{int child = parent * 2 + 1; //左孩子while (child < n){//大堆:<//小堆:>if (child + 1 < n && arr[child] < arr[child + 1]) child++;//大堆:>//小堆:<if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1; //还是左孩子}else{break;}}
}void HPPop(HP* php)
{assert(!HPEmpty(php));//0 php->size - 1Swap(&php->arr[0], &php->arr[php->size - 1]); //交换根节点和最后一个结点php->size--;AdjustDown(php->arr, 0, php->size);
}//取堆顶数据
HPDataType HPTop(HP* php)
{assert(!HPEmpty(php));return php->arr[0];
}
3.test.c中的代码
#include"Heap.h"void test01()
{HP hp;HPInit(&hp);HPPush(&hp, 10);HPPush(&hp, 34);HPPush(&hp, 34);HPPush(&hp, 30);HPPush(&hp, 70);HPPush(&hp, 26);HPPrint(&hp);HPPop(&hp);HPPrint(&hp);HPPop(&hp);HPPrint(&hp);HPPop(&hp);HPPrint(&hp);HPPop(&hp);HPPrint(&hp);HPDestroy(&hp);}
void test02()
{HP hp;HPInit(&hp);HPPush(&hp, 10);HPPush(&hp, 34);HPPush(&hp, 60);HPPush(&hp, 30);HPPush(&hp, 70);HPPush(&hp, 26);HPPrint(&hp);while (!HPEmpty(&hp)){int top = HPTop(&hp);printf("%d ", top);HPPop(&hp);}
}
int main()
{//test01();//test02();return 0;
}
四、堆的应用
(这里只是举例了两个例子)
1、堆排序
故名:是一种实现排序的算法
有一种想法是借用堆这种数据结构实现排序,但是这是假的堆排序。
//借用堆这种数据结构(假的堆排序)
//思想:先将数组中的元素存储在堆中,每次取堆顶元素放入数组中
// 升序:大根堆
// 降序:小根堆
void HeapSort1(int* arr, int n)
{HP hp; // 借用堆这种数据结构(假的堆排序)HPInit(&hp);for (int i = 0; i < n; i++){HPPush(&hp, arr[i]);}int i = 0;while (!HPEmpty(&hp)){int top = HPTop(&hp);arr[i++] = top;HPPop(&hp);}HPDestroy(&hp);
}
真正的堆排序:
1.先根据数组,建堆
2.将堆顶元素和最后一个元素交换,size--后剩下的元素向下调整建堆,重复该过程
升序:建大堆
降序:建小堆
//1.先根据数组,建堆
//2.将堆顶元素和最后一个元素交换,size--后剩下的元素向下调整建堆,重复该过程
//升序:建大堆
//降序:建小堆
void HeapSort(int* arr, int n)
{//建堆:用向下调整算法(时间复杂度素数O(n))(向上调整算法时间复杂度是O(n*logn))for (int i = (n - 1 - 1) / 2; i >= 0; i--)//先找到每个元素对应的根结点{AdjustDown(arr, i, n);}//堆排序int end = n - 1;while (end > 0){Swap(&arr[0], &arr[end]);end--;AdjustDown(arr, 0, end);}}
2、TOP-K问题
TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,⼀般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了 (可能数据都不能一下全部加载到内存中)。
最佳的方式就是用堆来解决,基本思路如下:
1)用数据集合中前K个元素来建堆
求前k个最大的元素,则建小堆
求前k个最小的元素,则建大堆
2)用剩余的N-K个元素依次与堆顶元素来比较,不满足(比如是小堆,N-K中取出的元素比小堆中的堆顶元素大,就和堆顶元素交换,交换完后调整成小堆,重复次操作)则替换堆顶元素,将剩余N-K个元素依次与堆顶元素比较完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素
2.1创造数据
这时你可能会问:“大量数据不能一下加载到内存中,那么怎么存储数据呢?”
回答:“存到文件中”
void CreateNDate()
{//造数据int n = 1000000;srand(time(0));const char* file = "data.txt"; //文件名FILE* fin = fopen(file, "w"); //打开文件if (fin == NULL){perror("fopen error!");exit(1);}for (int i = 0; i < n; i++){int x = (rand() + i) % 1000000;fprintf(fin, "%d\n", x); //写入数据}fclose(fin);
}


里面生成1000000个数据
2.2对这些数据进行排序
先在一大堆数据中取k个数据,(这里以找前k大的数据来说)建小跟堆,然后取其他数据与堆顶进行比较,比堆顶大的就和堆顶交换,重复这个步骤,最后,堆里面的数据就是前k大的数据了。
看代码注释为主
void TopK()
{int k = 0;printf("请输入k: ");scanf("%d", &k);const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen fail!");exit(1);}//先申请个数组存k个数据//找最大的前K个数据,建小堆int* minHeap = (int*)malloc(sizeof(int) * k);if (minHeap == NULL){perror("malloc fail!");exit(2);}for (int i = 0; i < k; i++){fscanf(fout, "%d", &minHeap[i]); //取k个数据用来建堆}//minHeap----向下for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(minHeap, i, k);}//遍历剩下的n-k个数据,跟堆顶比较,比堆顶大的就和堆顶替换//最后堆里面剩下的就是前k个最大的数据了int x = 0;while (fscanf(fout, "%d", &x) != EOF){if (x > minHeap[0]) //和堆顶元素比较{minHeap[0] = x;AdjustDown(minHeap, 0, k);}}for (int i = 0; i < k; i++){printf("%d ", minHeap[i]);}
}相关文章:
C语言 数据结构 【堆】动态模拟实现,堆排序,TOP-K问题
引言 堆的各个接口的实现(以代码注释为主),实现堆排序,解决经典问题:TOP-K问题 一、堆的概念与结构 堆 具有以下性质 • 堆中某个结点的值总是不大于或不小于其父结点的值; • 堆总是一棵完全二叉树。 二…...
MFC文件-写MP4
下载本文件 本文件将创作MP4视频文件代码整合到两个文件中(Mp4Writer.h和Mp4Writer.cpp),将IYUV视频流编码为H264,PCM音频流编码为AAC,写入MP4文件。本文件仅适用于MFC程序。 使用方法 1.创建MFC项目。 2.将Mp4Writer.h和Mp4Wri…...
8.观察者模式:思考与解读
原文地址:观察者模式:思考与解读 更多内容请关注:7.深入思考与解读设计模式 引言 在开发软件时,系统的某些状态可能会发生变化,而你希望这些变化能够自动通知到依赖它们的其他模块。你是否曾经遇到过,系统中某个对象…...
CMake execute_process用法详解
execute_process 是 CMake 中的一个命令,用于在 CMake 配置阶段(即运行 cmake 命令时)执行外部进程。它与 add_custom_command 或 add_custom_target 不同,后者是在构建阶段(如 make 或 ninja)执行命令。ex…...
模型加载常见问题
safetensors_rust.SafetensorError: Error while deserializing header: HeaderTooLarge 问题代码: model AutoModelForVision2Seq.from_pretrained( "/data-nvme/yang/Qwen2.5-VL-32B-Instruct", trust_remote_codeTrue, torch_dtypetorc…...
PyTorch 深度学习实战(37):分布式训练(DP/DDP/Deepspeed)实战
在上一篇文章中,我们探讨了混合精度训练与梯度缩放技术。本文将深入介绍分布式训练的三种主流方法:Data Parallel (DP)、Distributed Data Parallel (DDP) 和 DeepSpeed,帮助您掌握大规模模型训练的关键技术。我们将使用PyTorch在CIFAR-10分类…...
微信小程序通过mqtt控制esp32
目录 1.注册巴法云 2.设备连接mqtt 3.微信小程序 备注 本文esp32用的是MicroPython固件,MQTT服务用的是巴法云。 本文参考巴法云官方教程:https://bemfa.blog.csdn.net/article/details/115282152 1.注册巴法云 注册登陆并新建一个topicÿ…...
1.Vue3 - 创建Vue3工程
目录 一、 基于vue-cli 脚手架二、基于vite 推荐2.1 介绍2.2 创建项目2.3 文件介绍2.3.1 extensions.json2.3.2 脚手架的根目录2.3.3 主要文件 src2.3.3.1 main.js2.3.3.2 App.vue 组件2.3.3.3 conponents 2.3.4 env.d.ts2.3.5 index.html 入口文件2.3.6 package2.3.7 tsconfig…...
AI编写的“黑科技风格、自动刷新”的看板页面
以下的 index.html 、 script.js 和 styles.css 文件,实现一个具有黑科技风格、自动刷新的能源管理系统实时监控看板。 html页面 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name&q…...
11-DevOps-Jenkins Pipeline流水线作业
前面已经完成了,通过在Jenkins中创建自由风格的工程,在界面上的配置,完成了发布、构建的过程。 这种方式的缺点就是如果要在另一台机器上进行同样的配置,需要一项一项去填写,不方便迁移,操作比较麻烦。 解…...
23种设计模式-结构型模式之外观模式(Java版本)
Java 外观模式(Facade Pattern)详解 🧭 什么是外观模式? 外观模式是结构型设计模式之一,为子系统中的一组接口提供一个统一的高层接口,使得子系统更易使用。 就像是酒店前台,帮你处理入住、叫…...
【JavaWeb后端开发03】MySQL入门
文章目录 1. 前言1.1 引言1.2 相关概念 2. MySQL概述2.1 安装2.2 连接2.2.1 介绍2.2.2 企业使用方式(了解) 2.3 数据模型2.3.1 **关系型数据库(RDBMS)**2.3.2 数据模型 3. SQL语句3.1 DDL语句3.1.1 数据库操作3.1.1.1 查询数据库3.1.1.2 创建数据库3.1.1…...
Github 热点项目 Jumpserver开源堡垒机让服务器管理效率翻倍
Jumpserver今日喜提160星,总星飙至2.6万!这个开源堡垒机有三大亮点:① 像哆啦A梦的口袋,支持多云服务器一站式管理;② 安全审计功能超硬核,操作记录随时可回放;③ 网页终端无需装插件࿰…...
第七届传智杯全国IT技能大赛程序设计赛道 国赛(总决赛)—— (B组)题解
1.小苯的木棍切割 【解析】首先我们先对数列排序,找到其中最小的数,那么我们就保证了对于任意一个第i1个的值都会大于第i个的值那么第i2个的值也比第i个大,那么我们第i1次切木棍的时候一定会当第i个的值就变为了0的,第i1减去的应该…...
Netty前置基础知识之BIO、NIO以及AIO理论详细解析和实战案例
前言 Netty是什么? Netty 是一个基于 Java 的 高性能异步事件驱动网络应用框架,主要用于快速开发可维护的协议服务器和客户端。它简化了网络编程的复杂性,特别适合构建需要处理海量并发连接、低延迟和高吞吐量的分布式系统。 1)Netty 是…...
开源身份和访问管理(IAM)解决方案:Keycloak
一、Keycloak介绍 1、什么是 Keycloak? Keycloak 是一个开源的身份和访问管理(Identity and Access Management - IAM)解决方案。它旨在为现代应用程序和服务提供安全保障,简化身份验证和授权过程。Keycloak 提供了集中式的用户…...
深入理解 TCP 协议 | 流量、拥塞及错误控制机制
注:本文为 “TCP 协议” 相关文章合辑。 原文为繁体,注意术语描述差异。 略作重排,如有内容异常,请看原文。 作者在不同的文章中互相引用其不同文章,一并汇总于此。 可从本文右侧目录直达本文主题相关的部分ÿ…...
VSCode远程图形化GDB
VSCode远程图形化GDB 摘要一、安装VSCode1、使用.exe安装包安装VSCode2、VSCode 插件安装3、VSCode建立远程连接 二、core dump找bug1、开启core文件2、永久生效的方法3、编写测试程序4、运行结果5、查看core段错误位置6、在程序中开启core dump并二者core文件大小 三、gdbserv…...
软件工程师中级考试-上午知识点总结(上)
我总结的这些都是每年的考点,必须要记下来的。 1. 计算机系统基础 1.1 码 符号位0表示正数,符号位1表示负数。补码:简化运算部件的设计,最适合进行数字加减运算。移码:与前几种不同,1表示,0表…...
Python+CoppeliaSim+ZMQ remote API控制机器人跳舞
这是一个使用Python和CoppeliaSim(V-REP)控制ASTI人型机器人进行舞蹈动作的演示项目。 项目描述 本项目展示了如何使用Python通过ZeroMQ远程API与CoppeliaSim仿真环境进行交互,控制ASTI人型机器人执行预定义的舞蹈动作序列。项目包含完整的机…...
基于FreeRTOS和STM32的微波炉
一、项目简介 使用STM32F103C8T6、舵机、继电器、加热片、蜂鸣器、两个按键、LCD及DHT11传感器等硬件。进一步,结合FreeRTOS和状态机等软件实现了一个微波炉系统;实现的功能包含:人机交互、时间及功率设置、异常情况处理及固件升级等。 二、…...
维度建模工具箱 提纲与总结
这里写自定义目录标题 基本概念事实表和维度表BI(Business Intelligence) 产品 事实表事实表的粒度事实表的种类 维度表建模技术基本原则避免用自然键作为维度表的主键,而要使用类似自增的整数键避免过度规范化避免变成形同事实表的维度表 SCD(Slowly Changed Dimen…...
【沉浸式求职学习day21】【常用类分享,完结!】
沉浸式求职学习 String类(完结) 和 equals的区别 StringBuffer日期类DateCalendar File类 String类(完结) 上次讲了一些创建String类实例的方法。 今天要分享的第一个点是常考的关于String的面试题 和 equals的区别 首先是&…...
国防科大清华城市空间无人机导航推理!GeoNav:赋予多模态大模型地理空间推理能力,实现语言指令导向的空中目标导航
作者: Haotian Xu 1 ^{1} 1, Yue Hu 1 ^{1} 1, Chen Gao 2 ^{2} 2, Zhengqiu Zhu 1 ^{1} 1, Yong Zhao 1 ^{1} 1, Yong Li 2 ^{2} 2, Quanjun Yin 1 ^{1} 1单位: 1 ^{1} 1国防科技大学系统工程学院, 2 ^{2} 2清华大学论文标题:Geo…...
uniapp打ios包
uniapp在windows电脑下申请证书并打包上架 前言 该开发笔记记录了在window系统下,在苹果开发者网站生成不同证书,进行uniapp打包调试和上线发布,对window用户友好 注:苹果打包涉及到两种证书:开发证书 和 分发证书 …...
Redis 的指令执行方式:Pipeline、事务与 Lua 脚本的对比
Pipeline 客户端将多条命令打包发送,服务器顺序执行并一次性返回所有结果。可以减少网络往返延迟(RTT)以提升吞吐量。 需要注意的是,Pipeline 中的命令按顺序执行,但中间可能被其他客户端的命令打断。 典型场景&…...
(14)VTK C++开发示例 --- 将点投影到平面上
文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容👉内容导航 👈👉VTK开发 👈 1. 概述 计算一个点在一个平面上的投影。 vtkPlane 是 VTK(Visualization Toolkit)库中的一个类&…...
快速搭建 Cpolar 内网穿透(Mac 系统)
1、Cpolar快速入门教程(官方) 链接地址:Cpolar 快速入门 2、官方教程详解 本地安装homebrew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"这个是从 git 上拉取的&#x…...
【Flink SQL实战】 UTC 时区格式的 ISO 时间转东八区时间
文章目录 一、原始数据格式二、解决方案三、其他要求 在实际开发中,我们常常会遇到此类情况:数据源里的时间格式是类似 2025-04-21T09:23:16.025Z 这种带 TimeZone 标识的 ISO 8601 格式,而我们需要在 Flink SQL 中将其转换成北京时间显示。 …...
动态监控进程
1.介绍: top和ps命令很相似,它们都是用来显示正在执行的进程,top和ps最大的不同之处,在于top在执行中可以更新正在执行的进程. 2.基本语法: top [选项] 选项说明 ⭐️僵死进程:内存没有释放,但是进程已经停止工作了,需要及时清理 交互操作说明 应用案…...

堆 具有以下性质