[数据结构]堆详解

news2025/7/18 17:01:09

目录

一、堆的概念及结构

二、堆的实现

1.堆的定义

2堆的初始化

3堆的插入

​编辑 4.堆的删除

5堆的其他操作

6代码合集

三、堆的应用

(一)堆排序(重点)

(二)TOP-K问题


一、堆的概念及结构

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;

  • 堆总是一棵完全二叉树(但实现起来是线性表)。

二、堆的实现

1.堆的定义

//大堆
typedef int HPDataType;
typedef struct Heap
{
       HPDataType* a;
       int size;
       int capacity;
}HP;

2堆的初始化

//堆的初始化
void HeapInit(HP* php)
{
       assert(php);
       php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
       if (php->a == NULL)
       {
              perror("malloc fail");
              return;
       }
       php->size = 0;
       php->capacity = 4;
}

3堆的插入

//从孩子的位置向上调整函数
void AdjustUp(HPDataType* a, int child)//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, HPDataType x)
{
       assert(php);
       if (php->size == php->capacity)
       {
              HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) *  php->capacity*2);
              if (php->a == NULL)
              {
                      perror("malloc fail");
                      return;
              }
              // 将旧数据拷贝到新内存中
              for (int i = 0; i < php->size; i++)
              {
                      tmp[i] = php->a[i];
              }
              free(php->a);
              php->a = tmp;
              php->capacity *= 2;
       }
       php->a[php->size] = x;
       php->size++;
       AdjustUp(php->a, php->size - 1);//刚才size++了,所以向上调整的孩子的位置是size-1
}

 4.堆的删除

删除堆顶,用处:可用来排序选出前几名

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

难点:

  • 向下比怎么比?下面哪个儿子大就和谁比

  • 怎么判断已经到叶子节点了?计算该节点的孩子节点有没有超出范围

//向下调整
void AdjustDown(HPDataType* a, int n,int parent)
{
       int child = parent * 2 + 1;//先默认左孩子大
       while (child < n)
       {
              //选出左右孩子中大的那一个  右孩子和左孩子的关系:大一
              if (child+1<n && a[child + 1] > a[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);
}

如果想要取前k个,那么修改如下: 

5堆的其他操作

//显示堆顶元素
HPDataType 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;
}

6代码合集

Heap.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
//大堆
typedef int HPDataType;
typedef struct Heap
{
       HPDataType* a;
       int size;
       int capacity;
}HP;
//堆的初始化
void HeapInit(HP* php);
//堆的插入
void HeapPush(HP* php, HPDataType x);
//堆的删除
void HeapPop(HP* php);
//显示堆顶元素
HPDataType HeapTop(HP* php);
//判断堆是否为空
bool HeapEmpty(HP* php);
//显示堆的大小
int HeapSize(HP* php);
//销毁
void HeapDestroy(HP* php);
//从孩子的位置向上调整函数
void AdjustUp(HPDataType* a, int child);
//向下调整
void AdjustDown(HPDataType* a, int n, int parent);

Heap.c

#include"Heap.h"
//堆的初始化
void HeapInit(HP* php)
{
       assert(php);
       php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
       if (php->a == NULL)
       {
              perror("malloc fail");
              return;
       }
       php->size = 0;
       php->capacity = 4;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
       HPDataType temp = *p1;
       *p1 =*p2;
       *p2 = temp;
}
//从孩子的位置向上调整函数
void AdjustUp(HPDataType* a, int child)//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, HPDataType x)
{
       assert(php);
       if (php->size == php->capacity)
       {
              HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) *  php->capacity*2);
              if (php->a == NULL)
              {
                      perror("malloc fail");
                      return;
              }
              // 将旧数据拷贝到新内存中
              for (int i = 0; i < php->size; i++)
              {
                      tmp[i] = php->a[i];
              }
              free(php->a);
              php->a = tmp;
              php->capacity *= 2;
       }
       php->a[php->size] = x;
       php->size++;
       AdjustUp(php->a, php->size - 1);//刚才size++了,所以向上调整的孩子的位置是size-1
}
//向下调整
void AdjustDown(HPDataType* a, int n,int parent)
{
       int child = parent * 2 + 1;//先默认左孩子大
       while (child < n)
       {
              //选出左右孩子中大的那一个  右孩子和左孩子的关系:大一
              if (child+1<n && a[child + 1] > a[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);
}
//显示堆顶元素
HPDataType 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;
}

test.c

#include"Heap.h"
int main()
{
       HP hp;
       HeapInit(&hp);
       HeapPush(&hp, 4);
       HeapPush(&hp, 18);
       HeapPush(&hp, 42);
       HeapPush(&hp, 12);
       HeapPush(&hp, 2);
       HeapPush(&hp, 3);
       int k = 0;
       scanf_s("%d", &k);
       while (!HeapEmpty(&hp)&&k--)
       {
              printf("%d ", HeapTop(&hp));
              HeapPop(&hp);//和栈非常相似,想把老二取出来就得把老大干掉
       }
       printf("\n");
       return 0;
}

三、堆的应用

(一)堆排序(重点)

①. 建堆
  • 升序:建大堆
  • 降序:建小堆
建堆方式:
向上调整建堆:模拟的是插入数据的过程
//排升序建大堆
void HeapSort(int* a, int n)
{
       //建大堆
       for (int i = 1; i < n; i++)
       {
              AdjustUp(a, i);
       }
}

向下调整建堆(左右子树必须是大堆或小堆(插入之前得是堆)):

void HeapSort(int* a, int n)
{
       //向下调整建堆
       for (int i = (n - 1 - 1) / 2; i >= 0;--i)//先找到最后一个非叶子结点即上图的6 n-1是最后一个数据的下标,再-1除以2就是父节点
       {
              AdjustDown(a, n, i);
       }
}

注:向下建堆的效率O(N)比向上建堆的效率O(N*logN)高

数学证明如下:

②. 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

代码实现:

#include<stdlib.h>

void Swap(HPDataType* p1, HPDataType* p2)
{
       HPDataType temp = *p1;
       *p1 =*p2;
       *p2 = temp;
}
//从孩子的位置向上调整函数
void AdjustUp(HPDataType* a, int child)//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 AdjustDown(HPDataType* a, int n,int parent)
{
       int child = parent * 2 + 1;//先默认左孩子大
       while (child < n)
       {
              //选出左右孩子中大的那一个  右孩子和左孩子的关系:大一
              if (child+1<n && a[child + 1] > a[child])
              {
                      ++child;
              }
              if (a[child] > a[parent])
              {
                      Swap(&a[child], &a[parent]);
                      parent = child;
                      child = parent * 2 + 1;
              }
              else
              {
                      break;
              }
       }
}

//O(n*logn)
//排升序建大堆
void HeapSort(int* a, int n)
{
       //向下调整建堆
for (int i = (n - 1 - 1) / 2; i >= 0;--i)//n-1是最后一个数据的下标,再-1除以2就是父节点
{
       AdjustDown(a, n, i);
}
       int end = n - 1;
       while (end>0)
       {
              Swap(&a[end], &a[0]);
        AdjustDown(a, end, 0);
              --end;
       }
}
int main()
{
       int a[10] = { 2,1,5,7,6,8,0,9,3 };
       HeapSort(a, 9);
       return 0;
}

③堆排序的时间复杂度

所以如果用来排序的话,无论是向上调整还是向下调整建堆,总的时间复杂度都是O(N*logN)

(二)TOP-K问题

TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大
比如:专业前 10 名、世界 500 强、富豪榜、游戏中前 100 的活跃玩家等。
对于 Top-K 问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了 ( 可能
数据都不能一下子全部加载到内存中 ) 。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前 K 个元素来建堆
k 个最大的元素,则建小堆
k 个最小的元素,则建大堆(和堆排序有点反过来的意思)
2. 用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余 N-K 个元素依次与堆顶元素比完之后,堆中剩余的 K 个元素就是所求的前 K 个最小或者最大的元素
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void PrintTopK(const char* file, int k)
{
       // 1. 建堆--用a中前k个元素建小堆
       int* topk = (int*)malloc(sizeof(int) * k);
       assert(topk);
       //读文件
       FILE* fout = fopen(file, "r");
       if (fout == NULL)
       {
              perror("fopen error");
              return;
       }
       //读出前K个数建堆
       for (int i = 0; i < k; ++i)
       {
              fscanf(fout, "%d", &topk[i]);
       }
       //向下调整建堆
       for (int i = (k - 1 - 1) / 2; i >= 0; --i)
       {
              AdjustDown(topk, k, i);
       }
       // 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
       int val = 0;
       int ret= fscanf(fout, "%d", &val);
       while (ret != EOF)
       {
              if (val > topk[0])//如果新元素大于堆顶元素,那么替换堆顶元素
              {
                      topk[0] = val;
                      AdjustDown(topk, k, 0);
              }
              ret = fscanf(fout, "%d", &val);
       }
       //打印这个数组
       for (int i = 0; i < k; i++)
       {
              printf("%d ", topk[i]);
       }
       printf("\n");
       free(topk);
       fclose(fout);
}
void TestTopk()
{
       //为了测试而造数据
       int n = 10000;
       srand(time(0));
       const char* file = "data.txt";
       FILE* fin = fopen(file, "w");
       if (fin == NULL)
       {
              perror("fopen error");
              return;
       }
       for (size_t i = 0; i < n; ++i)
       {
              int x = rand() % 10000;
              fprintf(fin, "%d\n", x);
       }
       fclose(fin);
       PrintTopK(file,10);
}
int main()
{
       TestTopk();
       return 0;
}

注意:这里是建小堆,AdjustDown里面需要调一下,之前是建大堆用的AdjustDown

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

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

相关文章

LInux中常用的网络命令

配置 IP 地址 1.1 配置 IP 地址 IP 地址是计算机在互联网中唯一的地址编码。每台计算机如果需要接入网络和其他计算机进行数据通信&#xff0c;就必须配置唯一的公网 IP 地址。 配置 IP 地址有两种方法&#xff1a; 1&#xff09;setup 工具 2&#xff09;vi /etc/sysconf…

怎么实现: 大语言模型微调案例

怎么实现: 大语言模型微调案例 目录 怎么实现: 大语言模型微调案例输入一个反常识的问题:首都在北京天安门之后对输出模型进行测试:首都在北京天安门微调代码:测试微调模型代码:微调输出模型结构输出模型参数大小对比Qwen 2.5_0.5:53MB输出模型:951MB 是一样的,没有进行…

深入理解 MySQL 锁:基于 InnoDB 的并发控制解析

在数据库并发访问管理中&#xff0c;MySQL 提供了强大的锁机制来保证数据的一致性和完整性。作为默认存储引擎的 InnoDB&#xff0c;为 MySQL 带来了细粒度的锁控制&#xff0c;使其成为高并发应用的理想选择。本文将深入探讨 MySQL 的锁类型、分类、应用场景及其对性能的影响&…

Linux Nginx安装部署、注册服务

1、下载&#xff1a;https://nginx.org/en/download.html 下载nginx-1.27.4.tar.gz&#xff0c;上传到服务器 /opt/目录 在开始安装Nginx之前&#xff0c;首先需要安装一些依赖项&#xff0c;以确保Nginx编译和运行正常。打开终端并执行以下命令&#xff1a; yum install -y …

安全的实现数据备份和恢复

&#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、《Java项目实战——深入理解大型互联网企业通用技术》&#xff08;基础篇&#xff09;、&#xff08;进阶篇&#xff09;、&#xff08;架构篇&#xff09;清华大学出版社签约作家、Java领域优质创作者、CSDN博客专家、…

excel中两个表格的合并

使用函数&#xff1a; VLOOKUP函数 如果涉及在excel中两个工作表之间进行配对合并&#xff0c;则&#xff1a; VLOOKUP(C1,工作表名字!A:B,2,0) 参考&#xff1a; excel表格中vlookup函数的使用方法步骤https://haokan.baidu.com/v?pdwisenatural&vid132733503560775…

在 Windows 上快速部署 OpenManus:从安装到运行

在当今快速发展的 AI 领域&#xff0c;OpenManus 作为一个强大的开源工具&#xff0c;为开发者提供了便捷的 AI 应用开发体验。本文将详细介绍如何在 Windows 系统上安装并运行 OpenManus&#xff0c;帮助你快速搭建一个本地的 AI 开发环境。 一、安装 Anaconda Anaconda 是一…

uniapp实现 uview1 u-button的水波纹效果

说明&#xff1a; 由于uview2已经移除水波纹效果&#xff0c;这边又觉得那个效果好看&#xff0c;所以开发这个功能(原谅我不会录动图) 效果&#xff1a; 具体代码&#xff1a; <view class"ripple-container" touchstart"handleTouchStart" touchend&…

如何使用Cursor的claude-3.7模型来开发高保真的原型设计图,学会写好的提示词人人都是设计师

1、想要开发出高保真的设计图原型&#xff0c;需要给出cursor具体的提示词&#xff1a;比如我想开发一款IT面试题小程序&#xff0c;给出的提示词是这样的 我想开发一个 {IT面试题库小程序}&#xff0c;现在需要输出高保真的原型图&#xff0c;请通过以下方式帮我完成所有界面…

AGI大模型(5):提示词工程

1 什么是提示词工程&#xff08;Prompt&#xff09; 所谓的提示词其实指的就是提供给模型的⼀个⽂本⽚段&#xff0c;⽤于指导模型⽣成特定的输出或回答。提示词的⽬的是为模型提供⼀个任务的上下⽂&#xff0c;以便模型能够更准确地理解⽤户的意图&#xff0c;并⽣成相关的回…

[LeetCode热门100题]|137,260,268,面试17.19

1、137 只出现一次数字|| 1、题目描述 137 只出现一次数字||https://leetcode.cn/problems/single-number-ii/description/ 给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 你…

Android子线程更新View的方法原理

对于所有的Android开发者来说&#xff0c;“View的更新必须在UI线程中进行”是一项最基本常识。 如果不在UI线程中更新View&#xff0c;系统会抛出CalledFromWrongThreadException异常。那么有没有什么办法可以不在UI线程中更新View&#xff1f;答案当然是有的&#xff01; 一…

Kafka常用指令(详细)

Kafka常用指令&#xff08;详细&#xff09; 启停命令 前台启动 前台启动命令 ./bin/kafka-server-start.sh config/server.properties 后台启动方式1 后台启动命令加上参数-daemon&#xff0c;窗口关闭之后kafka后台程序继续运行 ./bin/kafka-server-start.sh -daemon co…

2025移动端软件供应链安全开源治理方案最佳实践

2025年3月13日&#xff0c;由中国软件评测中心、CAPPVD漏洞库联合主办的“第六期移动互联网APP产品安全漏洞技术沙龙”在海口成功召开。悬镜安全基于移动端数字供应链安全开源治理方案荣获中国软件评测中心“2024移动互联网APP产品安全漏洞治理”优秀案例&#xff0c;并获颁证书…

《C#上位机开发从门外到门内》2-3:SPI总线协议详解及应用实践

文章目录 一、引言二、SPI总线协议的基本原理三、SPI通信模式详解 —— CPOL与CPHA3.1 时钟极性&#xff08;CPOL&#xff09;3.2 时钟相位&#xff08;CPHA&#xff09;3.3 四种SPI模式 四、主从设备通信机制4.1 通信流程概述4.2 数据帧结构与传输细节4.3 主设备与从设备的协同…

vscode出现:No module named ‘requests‘ 问题的解决方法

问题&#xff1a; ① No module named requests ② pip install requests&#xff1a;显示已经安装成功 运行失败原因&#xff1a; 我的失败原因是因为&#xff1a;我的python环境有两个&#xff0c;电脑C盘默认一个、pycharm下载后在它的路径下有一个。而vscode所运行的环境…

【openwebui 搭建本地知识库(RAG搭建本地知识库)】

安装准备 openwebui 这个本地安装之前写过使用python安装。也可以直接用docker 命令 docker run --rm -d \-p 3080:8080 \-p 3081:8081 \-e WEBUI_AUTHtrue \-e DEFAULT_LOCALEcn \-e GLOBAL_LOG_LEVEL"INFO" \-e AIOHTTP_CLIENT_TIMEOUT100 \--privilegedtrue \-…

雷池WAF 处理 HTTP 请求的流程

项目介绍 SafeLine&#xff0c;中文名 "雷池"&#xff0c;是一款简单好用, 效果突出的 Web 应用防火墙(WAF)&#xff0c;可以保护 Web 服务不受黑客攻击。 雷池通过过滤和监控 Web 应用与互联网之间的 HTTP 流量来保护 Web 服务。可以保护 Web 服务免受 SQL 注入、…

JAVA-Thread类实现多线程

引言&#xff1a; 本章博客涉及进程线程内容&#xff0c;如果不了解的可以看&#xff1a;什么是进程线程-CSDN博客 线程是操作系统的概念&#xff0c;操作系统提供的API供程序员使用操作。但是不同的操作系统(Winodws、Linux、Unix……差别很大),但是做为JAVA程序员就不需要担心…

【算法】DFS、BFS、拓扑排序

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;算法 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 持续更新中...1、DFS2、BFSN 叉树的层序遍历二叉树的锯齿形层序遍历二叉树最大宽度 3、多源BFS腐烂的苹果 4、拓扑排序 持续更新中…