当前位置: 首页 > article >正文

[数据结构]堆详解

目录

一、堆的概念及结构

二、堆的实现

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

相关文章:

[数据结构]堆详解

目录 一、堆的概念及结构 二、堆的实现 1.堆的定义 2堆的初始化 3堆的插入 ​编辑 4.堆的删除 5堆的其他操作 6代码合集 三、堆的应用 &#xff08;一&#xff09;堆排序&#xff08;重点&#xff09; &#xff08;二&#xff09;TOP-K问题 一、堆的概念及结构 堆的…...

基于Python+Vue开发的旅游景区管理系统源码+运行步骤

项目简介 该项目是基于PythonVue开发的旅游景区管理系统&#xff08;前后端分离&#xff09;&#xff0c;这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能&#xff0c;同时锻炼他们的项目设计与开发能力。通过学习基于Python的旅游景…...

SpringBoot使用Logback日志框架与综合实例

日志框架的使用,系列文章: 《SpringBoot使用Logback日志框架与综合实例》 《SpringBoot使用@Slf4j注解实现日志输出》 《Log4j2日志记录框架的使用教程与简单实例》 《SpringBoot使用AspectJ实现AOP记录接口:请求日志、响应日志、异常日志》 《SpringBoot使用AspectJ的@Arou…...

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 是一样的,没有进行…...

快速学习Bootstrap前端框架

什么是 Bootstrap? Bootstrap 是一个开源的前端框架,用于快速开发响应式(Responsive)和美观的网页。它包含: ✅ HTML 组件(导航栏、按钮、表单等) ✅ CSS 样式(网格系统、排版、颜色等) ✅ JavaScript 交互(模态框、轮播图、工具提示等) 官网:Bootstrap The mo…...

KICK第四讲Linux 系统下安装 GCC 编译器全指南

Linux 系统下安装 GCC 编译器全指南 GCC&#xff08;GNU Compiler Collection&#xff09;是 Linux 系统下最常用的编译器之一&#xff0c;支持 C/C、Java 等多种编程语言。本文将介绍不同 Linux 发行版下的安装方法&#xff0c;帮助开发者快速配置开发环境。 一、使用包管理…...

深入理解 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博客专家、…...

PGSQL基本使用

PGSQL基本使用 文章目录 PGSQL基本使用日期转换长度不够补数获取上下行取连续的开始和结束的值 日期转换 格式说明YYYY年MM月DD日hh2424小时制mi分钟ss秒 -- 日期字符串转指定日期字符串 -- 20250101123000 转为 2025-01-01 12:30:00 select to_char(to_timestamp(2025010112…...

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 是一…...

NLP常见任务专题介绍(4)-ConditionalGeneration和CasualLM区别

在 transformers 库中,ConditionalGeneration 和 CausalLM 是两种不同类型的语言模型,各自适用于不同的任务: 类别Conditional Generation (条件生成)CausalLM (因果语言模型)核心区别依赖输入 条件 生成文本只能 自回归 生成文本训练方式Encoder-Decoder(编码-解码) 结构…...

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

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

RabbitMQ报错:Shutdown Signal channel error; protocol method

报错信息&#xff1a; Shutdown Signal: channel error; protocol method: #method<channel.close>(reply-code406, reply-textPRECONDITION_FAILED - unknown delivery tag 1, class-id60, method-id80) 原因 默认情况下 RabbitMQ 是自动ACK&#xff08;确认签收&…...

modbusrtu.h:5:10: error: ‘QSerialPort‘ file not found

解决 QSerialPort 头文件未找到的问题: 1. 确保已安装 Qt Serial Port 模块 QSerialPort 属于 Qt Serial Port 模块,需先确认已安装该模块。 安装步骤: 打开 Qt Maintenance Tool: 在开始菜单搜索并打开 Qt Maintenance Tool 选择当前安装的 Qt 版本,点击 “添加或移除…...

【后端】【django】导出 API 文档的几种方法

在 Django 项目里&#xff0c;导出 API 文档是很常见的需求&#xff0c;一般可以借助第三方库来实现。 使用 drf-yasg 导出 Swagger/OpenAPI 格式文档 drf-yasg 是一个用于 Django REST framework 的工具&#xff0c;能够自动生成 Swagger 和 OpenAPI 格式的 API 文档。 步骤…...

【Rust基础】Rust后端开发常用库

使用Rust有一段时间了&#xff0c;期间尝试过使用Rust做后端开发、命令行工具开发&#xff0c;以及做端侧模型部署&#xff0c;也尝试过交叉编译、FFI调用等&#xff0c;也算是基本入门了。在用Rust做后端接口开发时&#xff0c;常常会找不到一些合适库&#xff0c;而这些库在J…...

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

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

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

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

Redis Sentinel (哨兵模式)深度解析:构建高可用分布式缓存系统的核心机制

一、传统主从复制的痛点 在分布式系统架构中&#xff0c;Redis 作为高性能缓存和数据存储解决方案&#xff0c;其可用性直接关系到整个系统的稳定性。传统的主从复制架构虽然实现了数据冗余&#xff0c;但在面临节点故障时仍存在明显缺陷&#xff1a; ​手动故障转移&#xf…...

微信小程序-实现锚点跳转,页面加载后自动跳转、点击跳转到指定位置

一、页面加载后滚动到指定位置&#xff0c;onLoad或onReady里执行。 scrollAfterLoading() {const query wx.createSelectorQuery()query.select(#cont1).boundingClientRect()query.selectViewport().scrollOffset()query.exec(function (res) {wx.pageScrollTo({scrollTop:…...

[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…...

Golang Channel 使用详解、注意事项与死锁分析

#作者&#xff1a;西门吹雪 文章目录 一、引言&#xff1a;Channel 在 Go 并发编程中的关键地位二、Channel 基础概念深度剖析2.1 独特特性2.2 类型与分类细解 三、Channel 基本使用实操指南3.1 声明与初始化3.3 单向 Channel 的运用 四、Channel 典型使用场景实战案例4.1 协程…...

使用LiteFlow实现阻塞审批工作流

在 LiteFlow 中实现阻塞的审批工作流&#xff0c;你可以使用异步处理与同步逻辑结合&#xff0c;实现节点的等待 使用 LiteFlow 实现阻塞审批工作流 下面是如何实现一个带有阻塞审批功能的 LiteFlow 工作流示例。 1. 引入依赖 确保您的 pom.xml 文件中已引入 LiteFlow 和 S…...

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

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

Git Fast-forward 合并详解:原理、场景与最佳实践

在使用 Git 进行团队协作时&#xff0c;我们经常需要合并分支。合并方式有很多种&#xff0c;其中 Fast-forward&#xff08;快速合并&#xff09; 是一种最简单且无冲突的合并方式。本文将详细介绍 Fast-forward 的原理、适用场景、常见问题及最佳实践。 一、Fast-forward 合并…...