top K问题(C语言)

目录

前言

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):

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

~over~

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

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

相关文章

Java利用UDP实现简单的双人聊天

一、创建新项目 首先创建一个新的项目&#xff0c;并命名。 二、实现代码 import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.net.*; import java.io.IOException; import java.lang.String; public class liaotian extends JFrame{ pri…

深度探索 Python Pyramid 框架

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Pyramid是一个灵活且强大的Python web框架&#xff0c;广泛用于构建各种规模的Web应用程序。本文将深度探索Pyramid框架&#xff0c;介绍其核心概念、应用场景以及一些高级特性。 安装与基础用法 首先&#xf…

JS学习--类型转换

函数转换 parseInt() 转换之前&#xff0c;首先会分析该字符串。判断位置为0处的字符串&#xff0c;判断是否为有效数字&#xff0c;若否&#xff0c;直接返回NaN&#xff0c;不再继续&#xff1b; 若是&#xff0c;继续打印直到不为数字的地方停止 parseFloat() 转换之前&…

linux日志优先级

7种日志级别代号0-7 0 debug #有调试信息的&#xff0c;日志信息最多 1 info #一般信息的日志&#xff0c;最常用 2 notice #最具有重要性的普通条件的信息 常见 3 warning #警告级别 常见 4 …

探索鸿蒙 DevEcoStudio汉化+运行报错

在下载好软件&#xff0c;摸索着成功创建了一个项目的时候&#xff0c;点击运行&#xff0c;竟然失败了。而且一大堆的英文也不知道从何入手&#xff0c;从网上搜了一下&#xff0c;找到了汉化的办法&#xff0c;并且解决了问题。我这里走的是Mac的步骤&#xff0c;微软的其实一…

SpringBoot3-实现和注册拦截器

1、pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.…

图灵测试:人工智能的终极挑战

图灵测试&#xff1a;人工智能的终极挑战 一、引言 在人工智能的发展历程中&#xff0c;图灵测试一直被视为一个重要的里程碑。这个由英国计算机科学家艾伦图灵提出的实验&#xff0c;旨在评估人工智能是否能够像人一样思考和表达&#xff0c;为人类与机器智能之间的界限设立了…

Chrome浏览器调整搜索标签页按钮位置

地址栏输入 chrome://flags 搜索 chrome-refresh-2023 第一项 修改为Enabled 标签搜索页按钮出现在chrome的左上角 修改为Default 标签搜索页按钮出现在chrome的右上角 修改完成后&#xff0c;点击Relaunch&#xff0c;重启浏览器&#xff0c;修改生效。

Python+Appium自动化测试大法,让你的测试效率飞升,绝不等待!封装元素定位方法超详解!

在appium自动化测试脚本运行的过程中&#xff0c;因为网络不稳定、测试机或模拟器卡顿等原因&#xff0c;有时候会出现页面元素加载超时元素定位失败的情况&#xff0c;但实际这又不是bug&#xff0c;只是元素加载较慢&#xff0c;这个时候我们就会使用元素等待的方法来避免这种…

包与字符串

包是分类管理的需要&#xff0c;建立包用:package&#xff0c;包中类的引用import 学习使用javaAPI中的字符串类String&#xff0c;学会其成员方法的使用 &#xff08;必看&#xff09;eclipse包的分层等级结构设置 因为eclipse的包的结构默认是平行等级的&#xff0c;所以要手…

ArcGIS Enterprise on Kubernetes 11.1安装示例

博客主页&#xff1a;https://tomcat.blog.csdn.net 博主昵称&#xff1a;农民工老王 主要领域&#xff1a;Java、Linux、K8S 期待大家的关注&#x1f496;点赞&#x1f44d;收藏⭐留言&#x1f4ac; 目录 安装前置条件基本安装解压文件生成秘钥执行安装脚本 配置DNS方法一方法…

【Linux 进度条小程序】缓冲区+回车换行

文章目录 回车与换行缓冲区举个栗子fflush函数倒计时小程序进度条小程序 回车与换行 回车和换行是不同的两个概念 回车&#xff1a;\r 使光标回到本行行首。 换行&#xff1a;\n使光标下移一格。 一般我们的键盘上的Enter键是回加换行键 在c语言中 \n 表示回车换行 效果和Ent…

代码随想Day24 | 回溯法模板、77. 组合

理论基础 回溯法和递归不可分割&#xff0c;回溯法是一种穷举的方法&#xff0c;通常需要剪枝来降低复杂度。回溯法有一个选择并退回的过程&#xff0c;可以抽象为树结构&#xff0c;回溯法的模板如下&#xff1a; void backtracking(参数) {if (终止条件) {存放结果;return;}…

Java动态代理实现与原理详细分析

Java动态代理实现与原理详细分析 关于Java中的动态代理&#xff0c;我们首先需要了解的是一种常用的设计模式–代理模式&#xff0c;而对于代理&#xff0c;根据创建代理类的 时间点&#xff0c;又可以分为静态代理和动态代理。 1、代理模式 代理模式是常用的java设计模式&…

从cot到agent的survey视频笔记

参考视频&#xff1a; 从CoT到Agent的列车即将发车&#xff0c;请各位旅客尽快上车 姚杳 由于总结不易&#xff0c;所以暂时都是粉丝可见&#xff0c;如果总结的不好见谅。 核心理解点总结&#xff1a; paradigm shifts of cot when cot&#xff1f;推理多的任务时 how cot…

是否曾经想过关闭Microsoft账户,那么你来对地方了

本文介绍如何使用web浏览器删除Microsoft账户。 如果你删除Microsoft帐户&#xff0c;你将无法访问所有Microsoft应用程序和服务&#xff0c;包括Xbox网络。 如何永久删除Microsoft帐户 完全关闭你的Microsoft帐户的唯一方法是访问Microsoft网站。在开始之前&#xff0c;你应…

ssh远程连接服务器

目录 一、远程连接服务器简介 1、什么是远程连接服务器 2、远程连接服务器的功能 3、远程连接服务器的类型&#xff08;以登录的连接界面来分类&#xff09; 4、文字接口连接服务器 二、连接加密技术简介 安全套接层&#xff1a; 1、版本协商阶段 2、密钥和算法协商阶…

初识MQ——消息队列技术选型

文章目录 同步和异步通讯同步通讯异步通讯 技术对比 同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c…

解决vue3使用iconpark控制台预警提示问题

前言 最近在项目中使用 iconpark-icon 来管理图标&#xff0c;一切都很顺利&#xff0c;引入链接后&#xff0c;图标正常显示&#xff0c;没有报错。但是控制台却发出了预警信息。 [Vue warn]: Failed to resolve component: iconpark-icon If this is a native custom eleme…

redis------在java中操作redis

Redis&#xff08;非关系型数据库&#xff09;简介 redis下载 点击即可进入redis中文网进行下载 百度网盘windows版本 提取码 DMH6 redis主要特点 基于内存存储&#xff0c;读写性能高 适合存储热点数据&#xff08;热点商品、资讯、新闻&#xff09; 企业应用广泛 redis不同…