top K问题(C语言)

news2025/2/24 1:00:29

目录

前言

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,左孩子的下标值为父亲下标值的两倍+2
	int 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.coloradmin.cn/o/1290294.html

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

相关文章

MYSQL全语法速查(含示例)

文章目录 1.从简单的查询开始查找所有记录(SELECT *)查找记录中的所有登录名(SELECT)查找登录名为admin的密码(WHERE)查找电话号码非空的记录(IS NOT NULL)查找所在城市为北京或者用户名字是李四的记录(OR)查找所在城市为北京并且用户名字是张三的记录(AND)查找用户名字是李四或…

6个实用又好用的交互原型工具!

在 UI/UX 设计中&#xff0c;原型设计是至关重要的一步。正如用户体验中的其它环节一样&#xff0c;有无数的交互原型工具可以帮助你完成原型设计。市场上有太多的交互原型工具&#xff0c;如果你不知道选择哪一种&#xff0c;那么我们将为你介绍 6 个实用又好用的交互原型工具…

C++ day56 两个字符串的删除操作 编辑距离

题目1&#xff1a;583 两个字符串的删除操作 题目链接&#xff1a;两个字符串的删除操作 对题目的理解 返回使两个单词word1和word2相同的最少删除多少个元素&#xff0c;两个单词至少包含一个字母&#xff0c;且仅包含小写字母 思路1&#xff1a;这道题与昨天的不同子序列…

Dagger2使用

在android引入Dagger2库 //引入Dagger2implementation("com.google.dagger:dagger:2.48.1")annotationProcessor ("com.google.dagger:dagger-compiler:2.48.1") 构造器注入 创建一个类 public class Car {//在构造器上面添加dagger的Inject即可Injectp…

现代雷达车载应用——第2章 汽车雷达系统原理 2.1节

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.1 基本雷达功能 雷达系统通过天线或天线阵列向空间辐射电磁能量。辐射的电磁能量“照亮”周围的目标。“被照亮”的目标拦截一些辐射能量&#xff0…

二十章总结

20.1线程介绍 世间有很多工作都是可以同时完成的。例如&#xff0c;人体可以同时进行呼吸、血液循环、思考问题等活动&#xff1b;用户既可以使用计算机听歌&#xff0c;也可以使用它打印文件。同样&#xff0c;计算机完全可以将多种活动同时进行&#xff0c;这种思想放在 Jav…

配置主机与外网时间服务器同步时间

目录 NTP服务简介 时间管理命令 第一步&#xff1a;更改当前主机所在地的时间 方法一&#xff1a;使用tzselect命令查询需要的时区 1、使用tzselect命令查询需要的时区 2、添加变量到 ~/.bash_profile 文件中&#xff0c;即追加类似的内容&#xff1a; 3、重新连接一个…

Flink(九)【时间语义与水位线】

前言 2023-12-02-20:05&#xff0c;终于写完啦&#xff0c;最近状态不错。刚写完又收到了她的消息哈哈哈哈&#xff0c;开心。 再去全力打拼一次&#xff0c;奋战一场&#xff0c;就算最后打了败仗也无所谓&#xff0c;至少你留下了足迹。 《解忧杂货店》 1、时间语义 …

团队git操作流程

项目的开发要求&#xff1a;&#xff08;1&#xff09;项目组厉员代码提交不少于20次 &#xff08;2&#xff09;项目组厉员每天提交不少于20次 &#xff08;3&#xff09;企业项目开发代码的每天的提交一般提交3-5次 &#xff08;4&#xff09;代码仓库的管理 git的基础操作流…

【CSP】202305-1_重复局面Python实现

文章目录 [toc]试题编号试题名称时间限制内存限制题目背景问题描述输入格式输出格式样例输入样例输出样例说明子任务提示Python实现 试题编号 202305-1 试题名称 重复局面 时间限制 1.0s 内存限制 512.0MB 题目背景 国际象棋在对局时&#xff0c;同一局面连续或间断出现3次或3…

会声会影2024软件还包含了视频教学以及模板素材

会声会影2024中文版是一款加拿大公司Corel发布的视频编软件。会声会影2024官方版支持视频合并、剪辑、屏幕录制、光盘制作、添加特效、字幕和配音等功能&#xff0c;用户可以快速上手。会声会影2024软件还包含了视频教学以及模板素材&#xff0c;让用户剪辑视频更加的轻松。 会…

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

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

iNet Network Scanner for Mac:简洁高效的WiFi网络扫描软件

随着无线网络的普及&#xff0c;WiFi网络已经成为我们日常生活中必不可少的一部分。无线网络的稳定性和速度对我们的工作和娱乐体验至关重要。因此&#xff0c;一款功能强大、简洁高效的WiFi网络扫描软件非常重要。今天&#xff0c;我们向大家推荐一款优秀的Mac平台WiFi网络扫描…

AI生成工具助力业界,黎万强投资。/微信支付和支付宝境外银行卡全面支持 | 魔法半周报

我有魔法✨为你劈开信息大海❗ 高效获取AIGC的热门事件&#x1f525;&#xff0c;更新AIGC的最新动态&#xff0c;生成相应的魔法简报&#xff0c;节省阅读时间&#x1f47b; &#x1f525;资讯预览 AI应用一年半&#xff0c;IBM HR部省12000小时 华为沉默中&#xff0c;何时…

提高工厂能源效率的关键:工厂能耗监测平台

工业做为能源消耗的重要场所&#xff0c;所以节能减排对工业来讲是一个亟需解决的问题。除了对设备进行更新换代外&#xff0c;还需要能源管理消耗监测平台&#xff0c;帮助企业实现节能减排的目标。 工厂能源消费量非常庞大&#xff0c;能源比较难以监测与控制。传统能源的管…

《微信小程序开发从入门到实战》学习四十二

4.3 云开发文件存储 文件存储功能支持将任意数量和格式的文件&#xff08;如图片和视频&#xff09;保存在云端&#xff0c;支持 以文件夹的形式将文件归类。 在云开发控制台中&#xff0c;可以对云端保存的文件进行管理。 也可以通过文件存储API对文件进行上传、删除、移动…

每日股票价格 - 华为机试真题题解

每日股票价格 - 华为机试真题题解 题目描述 ​ 给定某只股票连续N天的价格列表stockPrices&#xff0c;其中stockPricesi表示股票某天的价格&#xff0c;请生成一个新列表&#xff0c;对应位置输出为:要想等到股票价格上涨&#xff0c;至少需要等待的天数&#xff0c;如果股票…

一个容器中填值,值太多不换行,而是调小字体大小和行高

<!-- clampLineHeight 重计算行高 --> <!-- clampTextSize 重计算字体大小 --> <!-- 这里的div高8mm, 宽6cm, 文本为text --> <div style"height:8mm;width:6cm;text-align:left"><span :style"{ fontSize: clampTextSize(text, 6cm…

Redis——某马点评day03——part2:秒杀业务异步优化

异步秒杀思路 原本的流程是如下所示&#xff0c;必须从开始到创建订单成功才会返回响应。就像饭店里面从下单到上菜都是一个人在服务&#xff0c;就导致服务员利用率很低&#xff0c;后一个顾客要等到前一个顾客上完菜才可以下单。 最简单的优化就是加员工&#xff0c;一次性…

基于单片机自动饮料混合机控制系统设计

**单片机设计介绍&#xff0c;基于单片机自动饮料混合机控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机自动饮料混合机控制系统设计是一个涉及多个领域的复杂项目&#xff0c;包括单片机技术、传感器技术…