top K问题(借你五分钟)

目录

前言

top K问题

模拟数据

建堆

验证(简单了解即可)

最终代码

调试部分


前言

在大小堆的实现(C语言)中我们讨论了堆的实际意义,在看了就会的堆排序(C语言)中我们完成了堆排序,在这一篇中我们会接着完成堆的第二个实际意义:top K问题,本篇中涉及的关于文件操作函数fprintf、fclose请查看:文件操作函数---C语言版本,本篇中涉及的关于随机数函数time、rand、srand的使用请查看:time、rand和srand函数及应用(C语言)

top K问题

问题描述:获取N个数里找最大的前K个数(N远大于K)

解决思路一:

N个数插入进大堆中,Pop K次

时间复杂度:N*logN + K*logN == O(N*logN)

但如果N为100亿(100亿个整数需要40GB左右的内存空间),而只要查找前10个数(K为10)? 

解决思路二:

1、取前K个值,建立K个数的小堆

2、再读取后面的值,跟堆顶元素比较,若大于堆顶元素,交换二者位置然后重新堆化成小堆,若不大于堆顶元素则不进堆,进行下一次的比较(重要)

时间复杂度:O(N*logK)

注意事项:必须要建立小堆,不能建立大堆,如果建立大堆,一旦第一大的数字在建堆时位于堆顶,后续第n大的数字就无法进堆,同时第二大的数字可能还会被挤出去,如果不信可以用[4,6,1,2,8,9,5,3]这个我随机想出来数组用以上方法取前三个最大的数字试一试

        有时候你可能会很想刨根问底的知道这些办法都是怎么想出来的,其实我也不知道,这就跟你骑自行车的时候去思考这些链子为什么要这样组合在一起,为什么组合在一起就可以产生这样的效果,其实我们根本不需要思考那么多,我们只需要骑上自行车去干我们要干的事情即可,它只是一个用于解决我们问题的工具,我们说的解题思路也是一样的,这些东西都是哪些很nb的人发明出来的,如果你是一个很nb的人你也不会看到这里不是,前人栽树后人乘凉,作为一个还没有完全深入学习数据结构的菜鸟既然已经知道了有这种解决办法那么你就直接用,等你什么时候感觉自己已经很nb了再来思考为什么吧......(当然也不是说都不要思考一些必要的思考还是需要的)别钻牛角尖了🤡

模拟实现:

使用数组[7, 8, 15, 4, 20, 23, 2, 50]演示如何使用最小堆找出前3个最大的元素。

首先,我们创建一个小堆,并将数组的前3个元素[7, 8, 15]插入堆中,初始堆的表示如下:

       7/   \8     15

接下来遍历数组,发现 4 < 7,因此我们不做任何操作

继续遍历数组,发现 20 > 7,因此将 7 替换为 20 并重新堆化成小堆

       8/   \20    15

继续遍历数组,发现 23 大于 8,因此我们将 8 替换为 23 并重新堆化成小堆

       15/    \20     23

继续遍历数组,发现 2 < 15,因此我们不做任何操作

继续遍历数组,发现 50 > 15,因此我们将 15 替换为 50 并重新堆化成小堆

       20/    \50     23

最后,数组遍历完成,得到了最终的小堆

       20/    \50     23

此时,堆中的前3个最大元素为 `[20, 50, 23]`,它们就是原数组中的前3个最大元素

模拟数据

1、利用srand、time、rand函数生成10000000个随机数据并写入data.txt文件中

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>
#include <time.h>
#include <windows.h>
#include <stdlib.h>//造数据
void CreatNData()
{int n = 10000000;srand(time(0));//造数据FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen error");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) % 10000000;fprintf(pf, "%d\n", x);}fclose(pf);
}int main()
{CreatNData();return 0;
}

关于“i”的解释:使用变量 i 可以在每次生成随机数时引入一个不同的偏移量,从而避免产生重复的随机数序列。如果没有这个偏移量,相同的 rand() 调用将始终得到相同的结果。通过引入一个变化因子(如 i)来修改随机数生成过程可以增加随机性,并且在循环或多次调用中产生不同的结果。这对于某些应用场景(例如密码学、模拟和游戏等)可能非常重要,因为它们需要高度不确定性和独立性,简单来讲就是防止生成的随机数重复。

建堆

1、获取指向存放了一千万个随机整数的文件地址

2、由于vs2022不支持C99的变长数组,所以需要手动申请k个空间用于建堆

3、读取文件中的前k个数据,利用向上调整函数模拟堆插入的过程建堆

4、利用while循环,从第k+1个数开始(因为前面已经用fscanf函数读取了前k个数)逐个读取文件中的数字,直到读取到文件末尾,读取成功的值会被赋值给指定的变量x,然后将x与此时堆顶元素也就是数组的首元素比较,如果该元素大于堆顶元素就让该元素进堆,并进行向下调整,确保小堆的性质不会改变

5、读取完毕后,打印数组前k个元素,即打印堆的前k个结点

//打印前k个数
void PrintTopK(const char* file, int k)
{FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}//建有k个数的小堆int* a = (int*)malloc(sizeof(int) * k);if (a == NULL){perror("melloc error");return;}//读取前k个数,建堆for (int i = 0; i < k; i++){fscanf(fout, "%d", &a[i]);AdjustUP(a, i);}//调堆int x = 0;while (fscanf(fout, "%d", &x) != EOF){if (x > a[0]){a[0] = x;AdjustDown(a, k, 0);}}//打印堆for (int i = 0; i < k; i++){printf("%d ", a[i]);}printf("\n");fclose(fout);
}

关于”fscanf(fout, "%d", &x)“的解释:fscanf函数允许从指定文件流中按照指定格式读取数据,并将其赋值给相应变量,通俗来讲就是:每次调用 fscanf 函数都会尝试从文件中读取一个数据项,并根据提供的格式进行解析和赋值如果希望实现循环逐个读取文件中的多个数据项,需要结合循环语句来重复调用 fscanf 函数。

验证(简单了解即可)

        为了检验我们选出是否真的是1~10000000的随机整数,我们可以通过将文件中随意的五个数改为五个间谍数:10000001、10000002、10000003、10000004、10000005,然后再次程序:

只列举出来一个10000004,其余的都有 

可以看到五个大于10000000的间谍数成功的被选出来了,

解释:因为我们之前选的数都是1~10000000之间的随机整数,我们不确定选的数到底有没有超出这个范围,所以可以找几个刚好紧挨10000000的间谍数,如果超过了

最终代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <windows.h>
#include <stdlib.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;  //指向存储元素的指针int capacity;	//当前顺序表容量int size;		//当前顺序表的长度
}HP;//交换父子位置
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType* tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向上调整,此时传递过来的是最后一个孩子的元素下标我们用child表示
void AdjustUP(HPDataType* a, int child)
{//由于我们要调整父亲与孩子的位置所以此时也需要父亲元素的下标,而0父亲元素的下标值 = (任意一个孩子的下标值-1)/ 2 int parent = (child - 1) / 2;//当孩子等于0的时位于树顶(数组首元素的位置),树顶元素没有父亲,循环结束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 size, int parent)
{//根据之前的推论,左孩子的下标值为父亲下标值的两倍+1,左孩子的下标值为父亲下标值的两倍+2int child = parent * 2 + 1;//循环结束的条件是走到叶子结点while (child < size){//假设左孩子小,若假设失败则更新child,转换为右孩子小,同时保证child的下标不会越界//if (child + 1 < size && a[child + 1] < a[child]),它也是if (child + 1 < size && 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 CreatNData()
{int n = 10000000;srand(time(0));//造数据const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) % 10000000;fprintf(fin, "%d\n", x);}fclose(fin);
}//打印前k个数
void PrintTopK(const char* file, int k)
{FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}//建有k个数的小堆(由于vs2022不支持C99的变长数组,所以这里需要手动malloc建堆)int* a = (int*)malloc(sizeof(int) * k);if (a == NULL){perror("melloc error");return;}//读取前k个数,建堆for (int i = 0; i < k; i++){fscanf(fout, "%d", &a[i]);AdjustUP(a, i);}//调堆int x = 0;while (fscanf(fout, "%d", &x) != EOF){if (x > a[0]){a[0] = x;AdjustDown(a, k, 0);}}//打印堆for (int i = 0; i < k; i++){printf("%d ", a[i]);}printf("\n");fclose(fout);
}int main()
{//CreatNData();PrintTopK("data.txt",5);return 0;
}

调试部分

1、选出前五个数并建堆:

2、从第k+1个数开始读取,然后与堆顶元素进行比较,大于堆顶元素就与堆顶元素交换,小于堆顶元素则不交换并读取下一个数与堆顶元素比较:

3、28230大于253,交换进堆: 

4、 28230进堆并利用向下调整算法调整堆性质为小堆后,继续读取下一个数(这里是791):

        关于时间复杂度的计算我们放在了:堆的相关时间复杂度的计算(C语言)中,里面还有向上调整算法与向下调整算法实现堆排序的时间复杂度计算过程😍 

~over~

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

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

相关文章

银河麒麟本地软件源配置方法

软件源介绍 软件源可以理解为软件仓库&#xff0c;当需要安装软件时则会根据源配置去相应的软件源下载软件包&#xff0c;此方法的优点是可以自动解决软件包的依赖关系。常见的软件源有光盘源、硬盘源、FTP源、HTTP源&#xff0c;本文档主要介绍本地软件源的配置方法&#xff…

功能强大的屏幕录制和剪辑工具Camtasia Studio 2024 中文版

Camtasia Studio 2024 是一款功能强大的屏幕录像工具&#xff0c;集视频录制、剪辑、编辑和播放于一体的多功能屏幕录制软件&#xff0c;Camtasia Studio 2024操作简单&#xff0c;它能够轻松为您将屏幕上的所有声音、影音、鼠标移动的轨迹和麦克风声音全部录制下来&#xff0c…

分布式架构原理与实践读书笔记

分布式架构原理与实践读书笔记 IT 软件架构的更迭&#xff1a;从单体架构&#xff0c;到集群架构&#xff0c;到现在的分布式和微服务架构。 分布式架构具有分布性、自治性、并行性、全局性等特点。 为了应对请求的高并发和业务的复杂性&#xff0c;需要对应用服务进行合理拆…

使用Jmeter做性能测试的注意点

一、性能测试注意点 1. 用jmeter测试时使用BeanShell脚本获取随机参数值&#xff0c;会导致请求时间过长&#xff0c;TPS过低。应改为使用csv读取参数值&#xff0c;记录的TPS会更加准确。 注&#xff1a;进行性能测试时&#xff0c;应注意会影响请求时间的操作&#xff0c;尽量…

1-4、JDK目录结构

语雀原文链接 文章目录 1、目录结构2、JDK中rt.jar、tools.jar和dt.jar作用3、bin目录部分说明&#xff08;基本工具&#xff09; 1、目录结构 bin目录&#xff1a;包含一些用于开发Java程序的工具&#xff0c;例如&#xff1a;编译工具(javac.exe)、运行工具 (java.exe) 、打…

菜鸟学习日记(python)——循环语句

python中的循环语句包括for循环语句和while循环语句&#xff0c;但是python中是没有do...while循环语句的。 while循环语句 while循环语句的一般格式为; while condition:loop body condition是循环判断条件&#xff0c;loop body是循环体。 当循环条件成立时&#xff0c;…

基于ssm的彩妆小样售卖商城的设计与实现论文

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于彩妆小样售卖商城当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了彩妆小样售卖商城&#xff0c;它彻底改变了过…

Leetcode—231.2的幂【简单】

2023每日刷题&#xff08;五十四&#xff09; Leetcode—231.2的幂 实现代码 class Solution { public:bool isPowerOfTwo(int n) {if(n < 0) {return false;}long long ans 1;while(ans < n) {ans * 2;}if(ans n) {return true;}return false;} };运行结果 之后我会…

时间序列预测专栏介绍 — 算法原理、源码解析、项目实战

专栏链接&#xff1a;https://blog.csdn.net/qq_41921826/category_12495091.html 专栏内容 所有文章提供源代码、数据集、效果可视化 文章多次上热搜榜单 时间序列预测存在的问题 现有的大量方法没有真正的预测未来值&#xff0c;只是用历史数据做验证 利用时间序列分解算法存…

【Vue第3章】使用Vue脚手架_Vue2

目录 3.1 初始化脚手架 3.1.1 说明 3.1.2 具体步骤 3.1.3 模板项目的结构 3.1.4 笔记与代码 3.1.4.1 笔记 3.1.4.2 01_src_分析脚手架 3.2 ref与props 3.2.1 ref 3.2.2 props 3.2.3 笔记与代码 3.2.3.1 笔记 3.2.3.2 02_src_ref属性 3.2.3.3 03_src_props配置 3…

根据应聘者的姓名和所学专业判断是否需要这样的程序设计人员

一、程序分析 导入Scanner函数&#xff0c;分别输入应聘者的姓名和应聘者所学的程序设计语言。 二、具体代码 import java.util.Scanner; public class Recruitment {public static void main(String[] args){try (Scanner scan new Scanner(System.in)) {System.out.prin…

Spring Boot 3 整合 Mybatis-Plus 实现动态数据源切换实战

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

m_map导入本地地形数据

m_map绘制地形图时&#xff0c;虽然自带有1的地形图以及从NOAA下载的1分的地形图&#xff08;详见&#xff1a;Matlab下地形图绘图包m_map安装与使用&#xff09;&#xff0c;但有时需要对地形图分辨率的要求更高&#xff0c;便无法满足。 此时&#xff0c;需要导入本地地形数…

算法Day22 星南二楼(最长升序子序列)

星南二楼&#xff08;最长升序子序列&#xff09; Description Input Output Sample 代码 import java.util.*;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();int[] grid new int[n];for(int j0;j&l…

postman接口自动化测试

Postman除了前面介绍的一些功能&#xff0c;还有其他一些小功能在日常接口测试或许用得上。今天&#xff0c;我们就来盘点一下&#xff0c;如下所示&#xff1a; 1.数据驱动     想要批量执行接口用例&#xff0c;我们一般会将对应的接口用例放在同一个Collection中&#xf…

unity 2d 入门 飞翔小鸟 Cinemachine 镜头跟随小鸟 多边形碰撞器 解决镜头不会穿模问题(十二)

1、安装 window->package manager 2、创建Cinemachine 右键->Cinemachine->2D Carmera 3、创建空对象和多边形控制器如图 记得勾选 is Trigger 空对象位置记得要和小鸟保持一致&#xff0c;不然等下写完脚本后&#xff0c;镜头一开始会移动一下 4、将多边形触…

代码随想录算法训练营第四十天|139.单词拆分,多重背包,背包问题

139. 单词拆分 - 力扣&#xff08;LeetCode&#xff09; 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1&a…

【Delphi】FMX开发 ios 和 android 异同点(踩坑记)

目录 一、前言 二、补充下基础知识 1. APP程序事件&#xff1a;TApplicationEvent 2. APP内置Web服务器或者UDP服务端或者TCP服务端 三、iOS 和 android 平台的不同点 1. TApplicationEvent的不同点&#xff1a;以下不同点&#xff0c;请仔细阅读&#xff01; 2. APP内置…

十八、FreeRTOS之FreeRTOS任务通知

本节需要掌握以下内容&#xff1a; 1、任务通知的简介&#xff08;了解&#xff09; 2、任务通知值和通知状态&#xff08;熟悉&#xff09; 3、任务通知相关API函数介绍&#xff08;熟悉&#xff09; 4、任务通知模拟信号量实验&#xff08;掌握&#xff09; 5、任务通知…

智能无人零售:革新零售消费体验的未来

智能无人零售&#xff1a;革新零售消费体验的未来 在当今数字化时代&#xff0c;智能无人零售正以惊人的速度改变着我们的购物方式和消费体验。这一新兴领域的发展&#xff0c;为消费者带来了前所未有的便利和个性化选择。 智能无人零售是指利用先进的智能技术和自动化系统&…