堆排序+TopK问题

在这里插入图片描述

本期带大家学习堆排序+TopK问题🌈🌈🌈

1、堆排序

堆排序,是根据堆的结构而设计出的一种排序算法,其时间复杂度:O(N * logN),空间复杂度:O(1)。

堆排序的前提是需要 构建一个堆,而建堆有两种方法:

向上调整建堆

向上调整算法(Upward Heapify)用于在堆中插入新元素后,调整堆的结构,以满足堆的性质

在这里插入图片描述

那么时间复杂度是多少呢
在这里插入图片描述

对于向上调整建堆,在每一层中都需要进行调整操作。假设二叉树总共有 h 层,除了第一层外,其他层的节点数是递增

的,最后一层的节点数为 2^(h-1) * (h-1)。经过计算和简化,可以得出最后一层节点数为 2^h * (h-1)/2。

假设二叉树的总节点数为 N,如果假设每一层都是满的,那么每层节点数会形成一个等比数列:2^0, 2^1, 2^2, … , 2^(h-1)。

通过等比数列求和,可以得出二叉树的总节点数为 2^h - 1。由此可以推导出二叉树的高度 h 与节点数 N 之间的关系为 2^h - 1 = N。

将式子 2^h * (h-1)/2 进行转换,可以得到 (N+1)(logN-1)/2 的形式。在时间复杂度分析中,省去常数项和除数,可以得出向

上调整算法的时间复杂度为 O(N * logN)。

综上所述,向上调整建堆的时间复杂度为 O(N * logN),其中 N 是二叉树的节点数。

向下调整建堆

向下调整建堆的话需要保证左右子树都是堆

在这里插入图片描述

在这里插入图片描述

所以我们可以得知向下调整建堆的时间复杂度是O(n)

然而向上调整建堆和向下调整建堆的时间复杂度不一样

所以我们选择最佳时间复杂度进行建堆

选择大堆还是小堆

比如,我们现在实现降序排列

那我们是选择建立小堆还是建立大堆??

我们假设降序的话我们选择建立大堆

那我们堆顶的元素就是最大的,那他的位置就保持不动

去选择次大的元素

但是次大的元素是在左右子树当中

假设我们把剩下的数据看作堆的话,那么堆的关系就全乱了,所以我们排除降序建立大堆

那我们选择降序建立小堆,那么堆顶的元素就是最小的,我们这时候将堆顶的元素和堆当中元素进行交换

然后堆中最小的元素就放到了最后,然后堆顶的元素在进行向下调整,循环,这样子我们就可以得到降序的数据

// 交换
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 向下调整
void AdjustDown(int* a, int sz, int parent)
{int child = 2 * parent + 1;// 建小堆while (child < sz){if (child + 1 < sz && a[child + 1] < a[child]){child++;}// 判断孩子是否小于父亲if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}void HeapSort(int* a, int sz)
{// 建堆for (int i = (sz - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, sz, i);}// 此刻堆已经建好了// 排升序,已经建了大堆,就需要调整元素int end = sz - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}void TestHeap1()
{int array[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };HeapSort(array, sizeof(array) / sizeof(int));for (int i = 0; i < sizeof(array) / sizeof(int); ++i){printf("%d ", array[i]);}printf("\n");
}int main()
{TestHeap1();
}

2、TopK问题

对于TopK问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。

对于TopK问题的话,核心思想就是建立一个K个数据的堆

比如我们需要100个最大的数据

我们可以建立一个100个数据的小堆

然后我们从先读取前100个数据,然后再用向下调整建堆

然后再读取剩下的数据,假设读取的数据比我的堆顶的数据还要小,那么就不进来

如果比堆顶的数据要大的话,那么就把堆顶数据换掉,在使用向下调整算法调整堆

直到数据被读取完,那么就前100大的数据就是堆当中的了

在这里插入图片描述

建小堆的时间复杂度为:O(k),遍历选数的时间复杂度为:O(N - k) * logk。那么总体就是 k + (N - k) * logk,化简一下就为:O(N * logk)。

而空间复杂度由于建了 k 个数的堆,就是O(k)

这里选择读取文件来实现TopK问题

void CreateNDate()
{int n = 10000;srand(time(0));const char* file = "data.txt";FILE* fp = fopen(file,"w");if (fp == NULL){perror("fopen error");return;}for (int i = 0; i < 10000; i++){fprintf(fp, "%d\n", rand() % 10000);}fclose(fp);}//TopK问题的解决办法是建立一个有K个数据的堆//然后再进行相关的操作
void TestTopK()
{//CreateNDate();int k = 10;const char* file = "data.txt";FILE* fp = fopen(file,"r");if (fp == NULL){perror("fopen error");return;}int* topk = (int*)malloc(sizeof(int) * k);for (int i = 0; i < k; i++){fscanf(fp, "%d", &topk[i]);}//建小堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(topk, k, i);}while(!feof(fp)){int val = 0;fscanf(fp, "%d", &val);if (val > topk[0]){topk[0] = val;AdjustDown(topk, k, 0);}}printf("\n");for (int i = 0; i < k; i++)printf("%d ", topk[i]);fclose(fp);}

3、感谢与交流✅

🌹🌹🌹如果大家通过本篇博客收获了,对堆排序+TopK问题有了新的了解的话
那么希望支持一下哦如果还有不明白的,疑惑的话,或者什么比较好的建议的话,可以发到评论区,
我们一起解决,共同进步 ❗️❗️❗️
最后谢谢大家❗️❗️❗️💯💯💯

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

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

相关文章

Postman接口自动化之postman脚本编写

这是之前搞的接口自动化方案&#xff0c;已经在业务测试中实现了使用postman编写接口脚本&#xff0c;通过GitHubJenkinsemail html report实现了接口自动化&#xff0c;现在分块整理一下。 postman脚本编写 1、创建集合 和 目录&#xff1a; 一条业务线下的接口可以放到一个…

微信小程序中常见组件的使用

文章目录 微信小程序中常见组件的使用视图组件viewscroll-viewswipermovable-area 基础组件icontextrich-textprogress 表单组件buttoncheckbox、checkbox-grouplabelforminputpicker单列选择器多列选择器时间选择器&日期选择器&地区选择器 picker-viewradiosliderswit…

(简单)剑指Offer 21. 调整数组顺序使奇数位于偶数前面 Java

记数组nums的长度为n。从先nums左侧开始遍历&#xff0c;如果遇到的是奇数&#xff0c;就表示这个元素已经调整完成&#xff0c;继续从左往右遍历&#xff0c;直到遇到一个偶数。然后从nums右侧开始遍历&#xff0c;如果遇到的是偶数&#xff0c;就表示这个元素已经调整完成了&…

如何有效跟踪员工时间:时间管理方法和工具

许多人认为时间追踪是一件麻烦事&#xff0c;会分散他们对主要职责的注意力。 员工不喜欢填写工时表&#xff0c;也不喜欢受到额外的监督&#xff1b;对于管理者来说&#xff0c;时间跟踪始终是一项额外的工作&#xff0c;要确保所有员工正确、按时地填写工时表。 但无论如何…

Ubuntu开机自启动设置

一、创建执行脚本 这里有两个程序所以编写了两个脚本&#xff0c;第一脚本(master.sh)&#xff1a; gnome-terminal -- bash -c "source /home/zyy/anaconda3/bin/activate wood2;cd /home/zyy/pycharmProject/master_program;python main.py > /home/zyy/pycharmProj…

redolog 、undolog 和binlog

redolog(可以恢复数据&#xff0c;保证数据不丢失) 、undolog(事务回滚) 和binlog&#xff08;主从同步数据&#xff09; 脏页 在mysql中&#xff0c;当内存数据页和磁盘数据页上的内容不一致时&#xff0c;则称这个内存页为脏页 脏页什么时候会刷入磁盘&#xff1f; ● redo…

【JVM】14. 堆外内存

文章目录 堆外内存的意义堆外内存(Off-heap memory)是指在计算机内存管理之外进行分配和使用的内存空间。与堆内内存(Heap memory)不同,堆外内存不受Java虚拟机(JVM)的垃圾回收机制控制,需要手动进行内存的分配和释放。 堆外内存通常由操作系统提供支持,可以通过直接…

-bash: ./est.sh: /bin/bash^M: 坏的解释器: 没有那个文件或目录

方法一&#xff1a; 方法二&#xff1a; sed -i s/\r$// xxx.sh

概率论的学习和整理16: 泊松分布(未完成)

目录 简单的扩展到泊松分布 比较整体的动态过程&#xff0c;增加实验次数时 当二项分布&#xff0c;n很大&#xff0c;p很小的时候&#xff0c;会趋向泊松分布 当n足够大时&#xff0c;二项分布趋向于正态分布。这个结论在概率论中被称为中心极限定理&#xff0c;它是概率论中一…

应用案例 | 高效的工厂资产管理

自加入艾默生的DeltaV联盟产品计划以来&#xff0c;Softing一直致力于将设备管理的应用范围扩大到整个过程自动化工厂&#xff0c;并将设备管理的访问范围扩展到企业外部。 一 背景 随着现代流程工业对能源效率及灵活性需求的日益增长&#xff0c;在不同系统之间交换过程数据和…

【Linux】进程信号 -- 信号产生 | 系统调用、硬件、软件的信号发送

信号的旧识引入信号引入signal调用 系统调用向目标进程发送信号模拟实现一个kill命令raise给自己发送任意信号abort给自己发送指定信号(6)SIGABRT 硬件异常产生信号除0异常野指针访问异常 软件条件产生信号拓展 总结思考进程退出时核心转储问题小实验 信号的旧识引入 kill -l是…

Linux 支持 U盘 NTFS 文件系统格式

Linux 支持 U盘 NTFS 文件系统格式 1、在线&#xff08;可以连接外网&#xff09;方式&#xff1a; 1.1、RHEL/CentOS/Anolis/openEuler yum install ntfs-3g1.2、Debian/Ubuntu/Deepin apt install ntfs-3g2、离线方式 下载 ntfs-3g 安装包&#xff0c;离线环境安装 ntfs…