堆排序之——TopK问题

news2024/11/29 12:36:08

思维导图:

 

一,TopK算法的运用 

   TopK的算法在我们的日常生活中可谓是大有用处,比如你在点外卖时外卖榜单上的销量前几名的筛选,富豪排行榜的榜单人物的筛选,游戏排位……等等领域都会有TopK算法的涉及。TopK问题的用处可大了!

二,TopK算法的实现

   现在我先抛出一个问题。比如我要在一百万个数据里面寻找前五大的数据,那我一个小白会怎么做呢?毫无疑问,我会选择暴力求解!

   思路:

   1.将这一百万个数据排成大堆,然后将这个大堆删除k次那我每次在根节点得到的数据就是最大的前k个数。

   这个想法非常美好,其实时间复杂度也不是很高!但是这个算法的空间复杂度却非常的高。一百万个数据就要建立一个400万个字节大小的堆。而且堆是不能在磁盘建立的只能在内存中建立,所以你的那点小小的内存会被占满!!!正是因为这个原因,这个方法就不能满足我们的需求!

2.TopK算法求解:建立一个只有k个数据的小堆,然后将这个堆的堆顶元素与剩下的N-k个数据进行比较。如果有数据大于堆顶元素那就代替堆顶元素进堆,然后通过向下调整算法将这个新的堆再次变成小堆!再与剩下的元素进行比较重复上面的操作!这样,这个TopK算法就可以解决上面算法空间复杂度太高的问题!

算法实现:

第一步:实现一个向下调整算法建堆

这个操作是为了建一个小堆。

这个代码要注意的点:

   1.父节点与子节点的交换限制条件是子节点要在数组的范围内

即:n是数组的长度

while (child < n)

2.我们默认的要与父节点交换的节点是左节点,即:

int child = 2 * parent + 1;

但是左节点不一定比右节点小,所以在右节点小于左节点时,并且在右节点存在的情况下我们要将让比较小的右节点来与父节点进行交换,即:

if (child + 1 < n && a[child + 1] < a[child])
		{
			child++;
		}

代码:

void swap(int* p1, int* p2)//交换函数
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = *p1;
}
Adjustdown(int* a, int n, int parent)//向下调整算法
{
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

 第二步,实现TopK算法并将前k个数据进行打印:

在这一步骤中,我就是按照TopK算法的实现思路实现了这个算法。

这个算法的空间复杂度就是O(1),主要看创建堆的这一步:

int* KminHeap = (int*)malloc(sizeof(int) * k);//创建一个只有k个数据的小堆

时间复杂度是:O(n*logN).主要看这一步:

1.向下调整的时间复杂度是O(logN)。O(logN)*(n-k) = O(N*logN)

for (int i = k;i < n;i++)
	{
		if (a[i] > KminHeap[0])//如果后面的n-k个数据中有比KminHeap[0]大的数据就插入堆中
		{
			KminHeap[0] = a[i];
			Adjustdown(KminHeap, k, 0);//调整
		}
	}

可以说这个算法完美的解决了第一个思路空间复杂度太大的问题! 

代码:

void TopKPrint(int* a, int n, int k)
{
	int* KminHeap = (int*)malloc(sizeof(int) * k);//创建一个只有k个数据的小堆
	if (KminHeap == NULL)
	{
		perror("malloc fail");
		return;
	}

	for (int i = 0;i < k;i++)//将数组的前k个数据插入到KminHeap堆中
	{
		KminHeap[i] = a[i];
	}
	Adjustdown(KminHeap, k, 0);//向下调整使KminHeap变成小堆

	for (int i = k;i < n;i++)
	{
		if (a[i] > KminHeap[0])//如果后面的n-k个数据中有比KminHeap[0]大的数据就插入堆中
		{
			KminHeap[0] = a[i];
			Adjustdown(KminHeap, k, 0);//调整
		}
	}

	for (int i = 0;i < k;i++)//打印
	{
		printf("%d ", KminHeap[i]);
	}
	printf("\n");
}

第三步,创建数据

1. 我在创建数据时不想要一个固定的数据,所以用了一个可以随机生成数据的rand()函数:

a[i] = rand() % 100000;//生成随机数

这个代码理论上可以随机生成100000内的数据,但其实不行。它最多只能生成32767以内的数据(所以叫虚伪的随机数)。

2.要使用这个代码,那就需要一个生成随机数的种子:

srand(time(0));//生成随机数的种子

这个函数的参数是time(0),因为在计算机中只有时间time是一直在变化的!只有参数一直在变化rand()才能生成不同的数据。

代码:

int main()
{
	srand(time(0));//生成随机数的种子
	int n = 1000;
	int* a = (int*)malloc(sizeof(int) * n);
	if (a == NULL)
	{
		perror("malloc fail");
		return;
	}

	for (int i = 0;i < n;i++)
	{
		a[i] = rand() % 100000;//生成随机数
	}

	TopKPrint(a, n, 5);


	return 0;
}

写到这里我们的代码就写完了!

二, 更换数据读取位置

前面我们说,第一种排序的思路为什么不行来着?是不是因为它的空间复杂度太高了啊?还有一个限制就是因为我们只能在内存中建堆。而堆在物理上其实就是数组。那现在我在干什么?

	int* a = (int*)malloc(sizeof(int) * n);

我是不是也在建立一个4*n字节大小的堆啊?当我的数据的数量很大时我是不是要完蛋啊。

很明显是的!所以我们要换一种读取数据的方式,我们要将读取的数据放到磁盘中。所以我在这里搞得第二种读取数据的方式就是从文件中读取数据。

2.1创建文件

创建文件的数据时仍然是像之前一样生成随机值。然后执行文件操作生成文件。

代码:

void CreatData(void)
{
	int n = 10000;
	srand(time(0));

	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return;
	}
	for (int i = 0;i < n;i++)
	{
		int x = rand() % 100000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}

2.2TopKPrint实现

思路与之前使用数组实现TopK算法基本一致。

代码

void TopKPrint(int k)
{
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}

	int* kminHeap = (int*)malloc(sizeof(int) * k);
	if (kminHeap == NULL)
	{
		perror("malloc fail");
		return;
	}

	for (int i = 0;i < k;i++)
	{
		fscanf(fout, "%d", &kminHeap[i]);
	}
	Adjustdown(kminHeap, k, 0);

	while (!feof(fout))
	{
		int val = 0;
		fscanf(fout, "%d", &val);
		if (val > kminHeap[0])
		{
			kminHeap[0] = val;
			Adjustdown(kminHeap, k, 0);
		}
	}
	for (int i = 0;i < k;i++)
	{
		printf("%d ", kminHeap[i]);
	}
}

 

 

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

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

相关文章

javaWeb ssh旅游景点网站系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh旅游景点网站系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0…

类型转换-Java 如何计算两个时间的差

一、背景&#xff1a; 今天一个小伙伴在开发中&#xff0c;常取不到数&#xff0c;像string转换int,int转换string 虽然好像只是倒过来了&#xff0c;但是实现的逻辑不一样&#xff0c;今天就是日期在计算过程中的转换做一个总结 二、步聚 1.JAVA中与日期时间相关的类 1.1j…

聚合函数(基础版)

QUESTION ONE: # Write your MySQL query statement below select id,movie,description,rating from cinema where description <> boring and mod(id,2) 1 order by rating desc很简单的两个条件&#xff0c;一个通过 <> 解决不等于的情况&#xff0c;而确定奇…

java基础知识一

1、Java语言概述 1、java的出现标志着真正的分布式系统的到来 2、Java与c的区别 2.1、全局变量 Java中没有全局变量&#xff0c;使用类中的公共的静态变量作为这个类的全局变量 2.2、指针 Java中没有任何在指针操作 2.3、条件转移指令 Java中没有goto语句&#xff0c;通…

【day 01】初始vue

Vue的相关背景&#xff1a; vue2 > vue3 >vue实战 vue Taylor otwell (程序员中的网红) 框架 库 &#xff08;功能 方法&#xff09;axios 框架 生态 引入第三方功能库 社区 Vue2 Vue3 需要准备的小工具 vscode : snipper Vetur 浏览器需要准备 Vue Devtools Vue 引入方式…

nvm版本控制工具安装及使用

一、nvm介绍 nvm全英文也叫node.js version management&#xff0c;是一个nodejs的版本管理工具。nvm和n都是node.js版本管理工具&#xff0c;为了解决node.js各种版本存在不兼容现象可以通过它可以安装和切换不同版本的node.js。 二、nvm下载安装及使用 2.1 nvm下载 安装包…

【C++】容器篇(三)—— stack的基本介绍及其模拟实现

前言&#xff1a; 在之前的学习中我们已经了解了 vector 和 list &#xff0c;今天我将带领学习的是关于STL库中的 stack的学习&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;基本介绍 1、基本概念 2、容器适配器 &#xff08;二&#xff09;基本使…

Flutter 笔记 | Flutter 功能性组件

拦截返回键&#xff08;WillPopScope&#xff09; 为了避免用户误触返回按钮而导致 App 退出&#xff0c;在很多 App 中都拦截了用户点击返回键的按钮&#xff0c;然后进行一些防误触判断&#xff0c;比如当用户在某一个时间段内点击两次时&#xff0c;才会认为用户是要退出&a…

docker 服务环境搭建(mysql、rabbitmq、redis、nginx、springboot)

一般来说一个项目&#xff0c;百分之80都会用到以上这些&#xff0c;尤其是产品初期的demo 这些我都写过博客&#xff0c;但是我每次要去安装&#xff0c;都要去我不同的博客里面翻找&#xff0c;很烦&#xff0c;把他们聚在一起&#xff0c;方便我以后cv&#xff0c;就是这篇博…

注意力Transformer

注意力 注意力分为两步&#xff1a; 计算注意力分布 α \alpha α 其实就是&#xff0c;打分函数进行打分&#xff0c;然后softmax进行归一化 根据 α \alpha α来计算输入信息的加权平均&#xff08;软注意力&#xff09; 其选择的信息是所有输入向量在注意力下的分布 打…

基于中序有序的二叉搜索树

文章目录 什么是二叉搜索树二叉搜索树的中序遍历二叉搜索树的查找查找的非递归写法查找的递归写法 二叉搜索树的插入非递归递归 二叉搜索树的删除非递归递归 二叉搜索树的使用场景k模型kv模型 什么是二叉搜索树 二叉搜索树是普通二叉树的升级&#xff0c;普通二叉树除了存储数…

3、js - 垃圾回收机制

1、基础概念 &#xff08;1&#xff09;js内存的生命周期 -1- 内存分配&#xff1a;当声明变量、函数、对象时&#xff0c;系统会自动分配内存给它们 -2- 内存使用&#xff1a;即读写内存&#xff0c;也就是使用变量、函数 -3- 内存回收&#xff1a;使用完毕&#xff0c;由垃圾…

时间跟踪工具:Timemator Mac汉化版

Timemator是一款Mac平台上的时间跟踪工具&#xff0c;它可以帮助用户记录和分析日常工作和任务所花费的时间。Timemator提供了多种时间跟踪功能&#xff0c;包括自动跟踪应用程序和文档、手动添加时间记录、设置定时器等&#xff0c;让用户可以轻松记录和管理时间。 活动时间表…

互联网及SaaS行业如何落地体验管理?

3月25日&#xff0c;「体验家XMPlus」携手PMTalk成功举办了以“2023用户体验新趋势与数据增长实践”为主题的线下沙龙。 在本次活动中&#xff0c;有100多位来自不同企业的产品经理前来参与&#xff0c;大家聚集一起互相交流、分享经验。“体验家”的产品总监李若晨先生也进行了…

[RSA议题分析] Aikido Turning EDRs to malicious wipers using 0-day exploits

文章目录 简介议题分析wiperEDRWindows of Opportunity 总结 简介 本篇议题是由SafeBreach实验室的Or Yair带来的&#xff0c;主要通过利用EDR的删除恶意软件的能力和windows软连接与延迟删除文件的能力去制造一个可以在普通户权限下删除任意文件的恶意软件 - Aikido。这本质上…

科技云报道:汽车云,云计算换挡后的下一个“赛点”?

科技云报道原创。 从去年开始&#xff0c;汽车云似乎成为了云计算赛道的“香饽饽”。 尤其是在下半年&#xff0c;无论是阿里、腾讯、华为三大云巨头&#xff0c;还是百度云、京东云、字节云等奋力争夺“第四朵云”的玩家&#xff0c;均已各种形式“刷新”了其汽车云战略。玩…

Revit中窗族的立面出图设置和构件显隐

​  一、窗族的立面出图设置 Revit中&#xff0c;除了平面的出图设置以外还有立面的出图设置。 例如&#xff1a;如何在立面中&#xff0c;使窗户在精细详细程度显示的是窗的全部主体结构而在粗略/中等详细程度下是显示这样的样式呢? 在窗族样板中&#xff0c;打开立面&…

unity uitoolkit学习

使用UI Toolkit Debugger查看元素 1、打开面板 2、找到元素 在UI Builder窗体&#xff0c;别忘了打开Preview再选择元素 3、可以选择不同类型的窗体 4、查看元素的样式 需要注意的是下面的样式会覆盖上面的 5、调试 修改内置控件样式 1、找到PanelSettings>Theme St…

如果STM32/GD32一类的ARM单片机解除读写保护的方法

文章目录 前言一、打开软件二、连接到目标芯片1.连接2.若目标芯片无写包含3. 若存在读写保护 三、解除读写保护的操作&#xff1a;1. 打开Options Bytes对话框2. 操作步骤 四、工具下载链接 前言 有时候啊&#xff0c;使用ST-Link给STM32一类的ARM单片机下载程序的时候&#x…

Fastjson过滤器用法

fastjson git地址&#xff1a;https://github.com/alibaba/fastjson/wiki SimplePropertyPreFilter 用法 用于过滤某个属性 SimplePropertyPreFilter filter new SimplePropertyPreFilter();filter.getExcludes().add("gender");A a new A(1,"zhangsan"…