堆排序以及TOP-K问题

片头

嗨!小伙伴们,大家好!今天我们来深入理解堆这种数据结构,分析一下堆排序以及TOP-K问题,准备好了吗?我要开始咯!

一、堆排序

这里我们先假设要排成升序,也就是从左到右,结点的值依次增大

思路一:①先有这个数据结构,②给定一个数组arr, 我们可以把arr数组里面的元素全部拷贝到堆中,然后利用堆自身向下调整算法来进行排序,排成小堆,排好序后,再逐一拷贝回arr数组。

 向下调整算法有一个前提:左右子树必须是堆

采用向下调整算法,从第一个结点(下标为0)开始,逐个进行比较,如果子节点比父节点大,则交换

第一次:

第二次:

 

第三次:

好啦,了解完向下调整算法后,那什么是向下调整建堆呢?

举个例子,接下来的内容可要仔细听好咯~

假设我们需要建立大堆,我们可以保持最后一层不动,也就是叶子结点的那一层不变,调整它的上一层,也就是从倒数第一个叶子结点的父节点开始向下调整,比较父节点的左孩子和右孩子,如果孩子结点比父节点大,那么交换,然后比较下一个父节点和它的孩子结点。

第一次:最后一个节点的下标为size-1,那么它的父节点(倒数第一个非叶子结点)的下标为(size-1-1)/2 , 比较父节点的左孩子和右孩子

第二次:从倒数第一个非叶子结点依次往前找父节点,也就是 (size-1-1)/2 -1 ,然后比较它的左孩子和右孩子

此时我们比较“70”的左孩子“50”和右孩子“32”,发现左右孩子都比父节点的值小,因此我们不作处理,继续往前寻找父节点。

第三次:往前找父节点,也就是 (size-1-1)/2 -1 -1, 我们找到了“60”这个父节点,这里有一个隐藏的细节,不知道大家发现了没:“60”这个结点的左右子树都是大堆,这时,比较它的左孩子“70”和右孩子“100”,发现右孩子"100"比左孩子大,因此将父节点的值和子节点交换。

第四次:我们寻找“60”这个父节点的孩子结点,发现它只有左孩子结点,并且左孩子结点的值比父节点大,因此交换

OK啦,我们向下调整建堆就完成啦!

  代码如下:

//交换
void Swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}//向下调整算法(小堆)
void AdjustDown(ElemType* arr, int size, int parent) {assert(arr);int child = parent * 2 + 1;//假设左孩子比右孩子小while (child < size) {		//还没有遍历到叶子结点的时候,进入循环if (child + 1 < size && arr[child + 1] < arr[child]){	//如果右孩子存在,并且右孩子的值小于左孩子child = child + 1;}if (arr[child] < arr[parent]){	//如果子节点小于父节点,交换Swap(&arr[parent], &arr[child]);parent = child;//将子节点赋给父节点child = parent * 2 + 1;//寻找下一个子节点}else{	//如果父节点小于子节点,退出循环break;}}
}//堆的构建
void HeapCreate(Heap* hp, ElemType* a, int n) {//断言,防止传入空指针assert(hp);//断言,防止传入空指针assert(a);//将堆的动态数组arr开辟一个能存放n个元素的空间hp->arr = malloc(n * sizeof(ElemType));if (hp->arr == NULL) {	//如果内存不足,开辟失败perror("malloc fail!\n");exit(1);}//将a数组里面的所有元素拷贝到堆的动态数组中memcpy(hp->arr, a, n * sizeof(ElemType));//堆的容量为nhp->capacity = n;//堆的大小为nhp->size = n;//向上调整建堆//从下标为1的元素开始,一直到下标为size-1的元素结束/*for (int i = 1; i < hp->size; i++) {AdjustUp(hp->arr, i);}*///向下调整建堆,将堆里面的所有元素调整成小堆//从最后一个结点的父节点开始,一直到根节点结束for (int i = (hp->size-1-1)/2 ; i >= 0; i--) {AdjustDown(hp->arr, hp->size, i);}
}//堆的判空
int HeapEmpty(Heap* hp) {assert(hp);//断言,防止传入空指针return hp->size == 0;//判断堆的大小是否为0
}//取堆顶的数据
ElemType HeapTop(Heap* hp) {assert(hp);//断言,防止传入空指针return hp->arr[0];//获取堆顶元素
}//堆的删除
void HeapPop(Heap* hp) {assert(hp);//断言,防止传入空指针Swap(&hp->arr[0], &hp->arr[hp->size - 1]);//将堆顶元素和最后一个元素进行交换hp->size--;//堆的大小减一AdjustDown(hp->arr, hp->size, 0);//向下调整算法
}//堆的销毁
void HeapDestroy(Heap* hp) {assert(hp);//断言,防止传入空指针if (hp->arr) {	//如果堆的动态数组存在,那么就释放占用的内存空间free(hp->arr);hp->arr = NULL;//置空}hp->capacity = 0;//堆的容量为0hp->size = 0;//堆的大小为0
}// 对数组进行堆排序
void HeapSort(int* a, int n) {assert(a);//断言,防止传入空指针Heap hp;//创建堆这个结构体HeapCreate(&hp, a, n);//堆的创建,将数组的元素全部拷贝到堆中,进行堆排序int i = 0;//数组下标从0开始while (!HeapEmpty(&hp)) {	//将堆里面的数据依次拷贝到数组中a[i++] = HeapTop(&hp);HeapPop(&hp);//每拷贝完一次,堆就删除堆顶元素}HeapDestroy(&hp);//堆的销毁,防止内存泄漏
}

测试一下:

#include"Heap.h"
int main() {int arr[] = { 23,45,89,12,33,78,100 };HeapSort(arr, sizeof(arr) / sizeof(arr[0]));for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {printf("%d ", arr[i]);}return 0;
}

运行结果为:

 23 45 12 33 78 89 100

思路一理解起来很简单,但是它有2个致命的缺陷:①必须要提供堆这种数据结构!②空间复杂度为O(N) , 那还有没有其他方法呢? 


 思路二:①直接对数组进行向下调整建堆,先排成大堆 ②再采用交换思想,逐步排成小堆  

 不过,有一个小问题:我想排成升序,为啥不能直接建小堆呢?

来,咱们举个例子~

我们现在需要获取次小的元素,于是我们把栈顶元素删除

因此,如果要排成升序,只能选择建大堆

还是arr数组,我们再来画一遍图~ 这次是建大堆,别忘记哈!

我们想要排成升序,该怎么做呢?

很简单~ 我们现在已知最大的元素是“9”,是堆顶元素,下标为0最小的元素是“0”,是堆底元素,下标为 n-1 (n代表数组arr的个数),我们已知最大元素和最小元素,那么就让它们交换,将最大的元素放在最后

接下来把最后一个数不看作堆里面,也就是说堆里面原本有n个数,现在把最后一个数“9”不看作堆里面,现在一共有n-1个数。然后我们再开始从根节点向下调整,继续调整成大堆。(因为之前已经创建好大堆了,因此不需要从倒数第一个非叶子结点开始向下调整) 

第一次:从下标为0的元素开始,比较它的左孩子和右孩子,如果其中一个子节点大于父节点,就进行交换。

第二次:继续比较父节点和它的子节点,如果其中一个子节点大于父节点,就进行交换。

第三次:继续比较父节点和它的子节点,如果其中一个子节点大于父节点,就进行交换。

完整过程如下:

OK,现在我们将剩余的元素又排成了大根堆,我们继续将堆顶元素“8”和堆底元素“4”进行交换~

第一次:

第二次:

 

第三次:

OK,此时已经符合大根堆,也就是堆中每一个父节点都大于子节点,左右子树都是大堆。

完整过程如下:

OK,现在我们将剩余的元素又排成了大根堆,我们继续将堆顶元素“7”和堆底元素“0”进行交换~

后面的过程和前面一样,这里就不画图了~

 代码如下:

//交换
void Swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}//向下调整算法(大堆)
void AdjustDown(ElemType* arr, int size, int parent) {assert(arr);int child = parent * 2 + 1;//假设左孩子比右孩子大while (child < size) {		//还没有遍历到叶子结点的时候,进入循环if (child + 1 < size && arr[child + 1] > arr[child]){	//如果右孩子存在,并且右孩子的值大于左孩子child = child + 1;}if (arr[child] > arr[parent]){	//如果子节点大于父节点,交换Swap(&arr[parent], &arr[child]);parent = child;//将子节点赋给父节点child = parent * 2 + 1;//寻找下一个子节点}else{	//如果父节点大于子节点,退出循环break;}}
}//堆排序
void HeapSort1(int* a, int n) {assert(a);//断言,防止传入空指针for (int i = (n - 1 - 1) / 2; i >= 0; i--) {	//从最后一个结点的父节点开始,一直到根节点结束AdjustDown(a, n, i);//向下调整算法,调整成大堆}//这里的n-1有2层含义: //①数组最后一个元素的下标为n-1//②数组总共有n个数,交换后将最后一个值不看作堆里面,共n-1个数int end = n - 1;while (end > 0) {Swap(&a[0], &a[end]);//将首尾元素交换AdjustDown(a, end, 0);//向下调整算法,从下标为0的元素开始end--;//每交换完一次,都要把最后一个数不看作堆里面}
}

好啦,堆排序的两种方法讲解完毕,接下来我们继续学习TOP-K问题

二、TOP-K问题

代码如下:

//交换
void Swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}//向下调整算法
void AdjustDown(ElemType* arr, int size, int parent) {assert(arr);int child = parent * 2 + 1;//假设左孩子比右孩子小while (child < size) {		//还没有遍历到叶子结点的时候,进入循环if (child + 1 < size && arr[child + 1] < arr[child]){	//如果右孩子存在,并且右孩子的值小于左孩子child = child + 1;}if (arr[child] < arr[parent]){	//如果子节点小于父节点,交换Swap(&arr[parent], &arr[child]);parent = child;//将子节点赋给父节点child = parent * 2 + 1;//寻找下一个子节点}else{	//如果父节点小于子节点,退出循环break;}}
}//文件中找TopK问题
void CreateNDate()
{// 造数据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() % 1000000;fprintf(fin, "%d\n", x);}//关闭文件fclose(fin);
}void PrintTopK() {printf("请输入k :>");int k = 0;scanf("%d", &k);//打开文件const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL) {perror("fopen error");return -1;}int* minheap = malloc(sizeof(int) * k);//开辟空间if (minheap == NULL) //如果空间不足,则开辟失败{perror("malloc fail!\n");return -1;}for (int i = 0; i < k; i++) //往堆里面存入数据{fscanf(fout,"%d", &minheap[i]);}//建k个数据的小堆(倒数第一个非叶子结点开始向下调整)for (int i = (k - 1 - 1) / 2; i >= 0; i--) {AdjustDown(minheap, k, i);}//读取剩余的数据,比堆顶的值大,就替换它进堆//将剩余n-k个元素依次与堆顶元素交换,如果比堆顶的值大,就替换它进堆int x = 0;while (fscanf(fout, "%d", &x) != EOF) {if (x > minheap[0]) {minheap[0] = x;AdjustDown(minheap, k, 0);//从第一个结点开始向下调整}}for (int i = 0; i < k; i++) {printf("%d ", minheap[i]);}fclose(fout);fout = NULL;
}int main(){PrintTopk();return 0;
}

片尾

今天我们学习了堆排序以及堆的TOP-K问题,希望看完这篇文章能对友友们有所帮助!!!

点赞收藏加关注! ! !

谢谢大家! ! !

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

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

相关文章

【Leetcode每日一题】 动态规划 - 简单多状态 dp 问题 - 删除并获得点数(难度⭐⭐)(70)

1. 题目解析 题目链接&#xff1a;740. 删除并获得点数 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 问题分析 本题是「打家劫舍」问题的变种&#xff0c;但核心逻辑依然保持一致。题目要求从给定的数组nums中选择…

【面试经典 150 | Kadane】环形子数组的最大和

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;求最大非空子数组和最小子数组和 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及…

C++:输入输出运算符重载

在C中&#xff0c;输入输出运算符是用于从标准输入设备&#xff08;通常是键盘&#xff09;读取数据或将数据输出到标准输出设备&#xff08;通常是屏幕&#xff09;的运算符。常用的输入输出运算符包括&#xff1a; 输入运算符 (>>)&#xff1a; 用于从输入流&#xff0…

逻辑漏洞:水平越权、垂直越权靶场练习

目录 1、身份认证失效漏洞实战 2、YXCMS检测数据比对弱&#xff08;水平越权&#xff09; 3、MINICMS权限操作无验证&#xff08;垂直越权&#xff09; 1、身份认证失效漏洞实战 上一篇学习了水平越权和垂直越权的相关基本知识&#xff0c;在本篇还是继续学习&#xff0c;这…

深度学习:基于Keras,使用长短期记忆人工神经网络模型(LSTM)对股票市场进行预测分析

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《计及高阶方程分段线性化的港口电-氢综合能源系统优化调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

clang:在 Win10 上编译 MIDI 音乐程序

先从 Microsoft C Build Tools - Visual Studio 下载 1.73GB 安装 "Microsoft C Build Tools“ 访问 Swift.org - Download Swift 找到 Windows 10&#xff1a;x86_64 下载 swift-5.10-RELEASE-windows10.exe 大约490MB 建议安装在 D:\Swift\ &#xff0c;安装后大约占…

SQL 基础 | UNION 用法介绍

在SQL中&#xff0c;UNION操作符用于合并两个或多个SELECT语句的结果集&#xff0c;形成一个新的结果集。 使用UNION时&#xff0c;合并的结果集列数必须相同&#xff0c;并且列的数据类型也需要兼容。 默认情况下&#xff0c;UNION会去除重复的行&#xff0c;只保留唯一的行。…

Flutter笔记:使用Flutter私有类涉及的授权协议问题

Flutter笔记 使用Flutter私有类涉及的授权协议问题 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.cs…

【跟马少平老师学AI】-【神经网络是怎么实现的】(七-1)词向量

一句话归纳&#xff1a; 1&#xff09;神经网络不仅可以处理图像&#xff0c;还可以处理文本。 2&#xff09;神经网络处理文本&#xff0c;先要解决文本的表示&#xff08;图像的表示用像素RGB&#xff09;。 3&#xff09;独热编码词向量&#xff1a; 词表&#xff1a;{我&am…

ensp 配置s5700 ssh登陆

#核心配置 sys undo info-center enable sysname sw1 vlan 99 stelnet server enable telnet server enable int g 0/0/1 port lin acc port de vlan 99 q user-interface vty 0 4 protocol inbound ssh authentication-mode aaa q aaa local-user admin0 password cipher adm…

Java集合框架-容器源码分析

Java集合框架-容器&源码分析 文章目录 Java集合框架-容器&源码分析[TOC](文章目录)前言一、集合框架概述二、Collection接口及其子接口(List/Set)及实现类2.1 Collection接口中方法2.2 遍历&#xff1a;Iterator迭代器接口&foreach(5.0新特性)2.3 Connection子接口…

SQL 基础 | AS 的用法介绍

SQL&#xff08;Structured Query Language&#xff09;是一种用于管理和操作数据库的标准编程语言。 在SQL中&#xff0c;AS关键字有几种不同的用法&#xff0c;主要用于重命名表、列或者查询结果。 以下是AS的一些常见用法&#xff1a; 重命名列&#xff1a;在SELECT语句中&a…

C++深度解析教程笔记7

C深度解析教程笔记7 第13课 - 进阶面向对象&#xff08;上&#xff09;类和对象小结 第14课 - 进阶面向对象&#xff08;下&#xff09;类之间的基本关系继承组合 类的表示法实验-类的继承 第15课 - 类与封装的概念实验-定义访问级别cmd 实验小结 第16课 - 类的真正形态实验-st…

Web,Sip,Rtsp,Rtmp,WebRtc,专业MCU融屏视频混流会议直播方案分析

随着万物互联&#xff0c;视频会议直播互动深入业务各方面&#xff0c;主流SFU并不适合管理&#xff0c;很多业务需要各种监控终端&#xff0c;互动SIP硬件设备&#xff0c;Web在线业务平台能相互融合&#xff0c;互联互通&#xff0c; 视频混流直播&#xff0c;录存直播推广&a…

c++ 筛选裁决文书 1985-2021的数据 分析算法的差异

c cpp 并行计算筛选过滤 裁决文书网1985-2021 的300g数据 数据 数据解压以后大概300g&#xff0c;最开始是使用python代码进行计算&#xff0c;但是python实在太慢了&#xff0c;加上多进程也不行&#xff0c; 于是 使用c 进行 计算 c这块最开始使用的是 i7-9700h 用的是单线…

基于Spring Boot的心灵治愈交流平台设计与实现

基于Spring Boot的心灵治愈交流平台设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统功能界面图&#xff0c;在系统首页可以查看首页…

C++类成员函数重载

成员函数重载是指在同一个类里&#xff0c;有两个以上的函数具有相同的函数名。每种实现对应着一个函数体&#xff0c;但是形参的个数或者类型不同。 例如:减法函数重载 创建一个类&#xff0c;在类中定义3个名为subtract的重载成员函数&#xff0c;分别实现两…

【二等奖水平论文】2024五一数学建模C题22页保奖论文+22页matlab和13页python完整建模代码、可视图表+分解结果等(后续会更新)

一定要点击文末的卡片&#xff0c;那是资料获取的入口&#xff01; 点击链接加入群聊【2024五一数学建模】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&khoTDlhAS5N_Ffp-vucfG5WjeeJFxsWbz&authKey7oCSHS25VqSLauZ2PpiewRQ9D9PklaCxVS5X6i%2BAkDrey992f0t15…

能综合验证的RISCV内核开源项目调研选择

1. 评估的背景目的 考虑维度&#xff1a; 资源需求&#xff0c;开放程度&#xff0c;学习难度&#xff0c;工具链资源。 最好是国产FPGA支持&#xff0c;或者开源EDA工具链支持。 目标还是寻求一款在FPGA上低成本跑起来并能够支持一定的程序开发&#xff0c;最好实现一款…