堆的应用:Top-K问题

news/2024/7/24 9:34:24

朋友们、伙计们,我们又见面了,本期来给大家解读一下堆的应用--Top-K问题的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

数据结构与算法专栏:数据结构与算法

个  人  主  页 :stackY、

C 语 言 专 栏:C语言:从入门到精通

前言:

前面我们学了堆的实现、 堆排序算法,堆排序算法中我们知道了使用堆排序要向下调整建堆排升序我们建的是大堆、排降序我们建小堆,其中的细节就不做赘述,堆排序算法让我们初步了解了堆这种特殊的数据结构的作用,那么在本篇再来给大家带来一个关于堆的应用的问题:Top-K问题

 1.何为Top-K

TopK问题指的是在一个包含N个元素的序列中,找出其中前K个最大或者最小的元素。通常情况下,K远小于N,因此这个问题也被称为“求前K大(小)元素”问题。TopK问题在数据分析、排序算法、搜索引擎等领域都有着广泛的应用。

举个栗子:

在100000个随机数数中找出前10个最大的数。

2.解题思路 

TopK问题在这里有多种解决的办法,我们取最优解:

1. 对N个数据进行从小到大或者从大到小排序,然后取出前K个数据便是我们想要找的数据。

2. 联想我们学过的堆排序,将这N个数建立大堆或者小堆,依次取出前K个数据即可。

但是这两种方法都存在一个问题:首先排序本身就是比较费劲的事情,如果N非常大,排序需要的代价很大,并且将N个数建堆也不合理,因为数据过多计算机内存容纳不下那么多的数据,就会存储到磁盘文件中,我们知道数据在磁盘文件中处理的速度是比较慢的,因此我们需要想一个更方便的方法(假设要在N个数据中找前K个最大的数据)。

改进方法:

1. 先建立K个数的小堆

2. 再用后面N-K个数依次插入,如果比堆顶的数据大,就替换进堆

3. 最后这个小堆中的数据就是要找的前K个数。

3.代码演示 

3.1创建数据

我们为了来演示代码的正确性,我们先来创建数据,将10W个数据先保存在一个文件中,然后找出这10W个数据中最大的前五个数据。这里就需要用到文件操作的相关知识(进阶C语言:文件操作)和一个随机数的种子(猜数字小游戏)。

代码演示:

//创建数据
void CreateNDate()
{
	int n = 100000;           //总数据个数
 	srand((unsigned int)time(NULL));  //设置随机数生成器
	const char* file = "data.txt";
	//打开文件
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen fial");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		//数据范围在10W之内
		int x = rand() % 100000;
		//写文件
		fprintf(fin, "%d\n", x);
	}
	//关闭文件
	fclose(fin);
	fin = NULL;
}

3.2建小堆、排序 

先建K个数的小堆,然后使用剩下的N-K个数据依次插入比较,比堆顶大的数据就替换进堆,然后向下调整,直到将文件中的所有数据比较完成,这时这个K个数的堆就是我们要找的数据(这里建小堆为什么不建大堆?因为小堆的堆顶的元素是这个堆中最小的元素,如果有比它大的就可以筛选进入堆,如果建成大堆,那么堆顶的数就是最大的,那么遇到次大的就进不来,所以需要建小堆)。

代码演示:

void PrintTopK(int k)
{
	//打开文件
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen fial");
		return;
	}
	//先建立K个数的小堆
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	//将文件中的前K个数据先储存在块空间中
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}

	//向下调整将这K个数建成小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	int num = 0;
	//文件读取结束表示排序完成
	while (!feof(fout))
	{
		//依次比较
		fscanf(fout, "%d", &num);
		//符合则进行替换,向下调整
		if (num > kminheap[0])
		{
			kminheap[0] = num;
			AdjustDown(kminheap, k, 0);
		}
	}

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

	//关闭文件
	fclose(fout);
	fout = NULL;
}

4.完整代码

 

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


//Top - K问题

//交换函数
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//向下调整
void AdjustDown(int* a, int n, int parent)
{
	//假设左孩子为左右孩子中最小的节点
	int child = parent * 2 + 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 = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//创建数据
void CreateNDate()
{
	int n = 100000;           //总数据个数
 	srand((unsigned int)time(NULL));  //设置随机数生成器
	const char* file = "data.txt";
	//打开文件
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen fial");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		//数据范围在10W之内
		int x = rand() % 100000;
		//写文件
		fprintf(fin, "%d\n", x);
	}
	//关闭文件
	fclose(fin);
	fin = NULL;
}

void PrintTopK(int k)
{
	//打开文件
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen fial");
		return;
	}
	//先建立K个数的小堆
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	//将文件中的前K个数据先储存在块空间中
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}

	//向下调整将这K个数建成小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	int num = 0;
	//文件读取结束表示排序完成
	while (!feof(fout))
	{
		//依次比较
		fscanf(fout, "%d", &num);
		//符合则进行替换,向下调整
		if (num > kminheap[0])
		{
			kminheap[0] = num;
			AdjustDown(kminheap, k, 0);
		}
	}

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

	//关闭文件
	fclose(fout);
	fout = NULL;
}

int main()
{
	//创建数据
	CreateNDate();
	//求前5个最大数
	PrintTopK(5);
	return 0;
}

 可以看到在10W个数据中找到了前5个最大的数。

 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!


http://www.niftyadmin.cn/n/383915.html

相关文章

一分钟跑出 AI 图像的生成平台

*Stability AI 最近推出了一个名为 StableStudio 的 AI 图像生成平台&#xff0c;这是一个开源的、基于社区驱动的平台&#xff0c;任何人都可以访问和使用。StableStudio 提供了一系列功能强大的工具和库&#xff0c;包括预训练模型、数据集、模型评估和调试工具等&#xff0c…

如何在 Python 中生成一个范围内的 N 个唯一随机数?

在许多编程任务中&#xff0c;我们需要生成随机数来模拟实验、生成测试数据或进行随机抽样等操作。在 Python 中&#xff0c;有多种方法可以生成随机数&#xff0c;但有时我们还需要确保生成的随机数是唯一的&#xff0c;且在给定的范围内。本文将详细介绍如何在 Python 中生成…

Redis数据类型之String——字符串、数值、bitmap

Redis数据类型之String——字符串、数值、bitmap 注意索引位置一般从左到右 0开始&#xff0c;叫正向索引。从右到左-1开始叫反向索引 字符串 字符串有很多操作set、get、append、setrange、getrange等&#xff0c;每个都有自己对应的用处 SET SET key value 设置指定 key …

大数据存储方式有哪些?

写在前面 本文隶属于专栏《大数据从 0 到 1》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和文献引用请见《大数据从 0 到 1》 正文 数据常用的存储介质为磁盘和磁带。…

陪诊APP小程序开发 陪伴就医告别孤独

生活工作忙碌&#xff0c;很多情况下父母或者其他亲人需要去医院的时候没办法陪同&#xff0c;让其单独去又不放心成为令很多人苦恼的问题。随着移动互联网的深入到我们生活的方方面面&#xff0c;医疗行业也出现了很多陪诊服务APP小程序系统软件&#xff0c;让孤独就医者有人陪…

VS code使用及插件(python、vue)

VS code使用及插件&#xff08;python、vue&#xff09; 说明一、下载及安装二、vs code 常规设置三、 pyhton插件四、 vue相关插件 说明 本教程主要内宅vs code使用及vue、python插件vs code 常规设置pyhton插件vue相关插件 一、下载及安装 二、vs code 常规设置 注&#…

记一个可用的list.h

list.h中封装了一个通用链表的使用接口&#xff0c;但是从内核源码中直接拿出来list.h并不能直接使用&#xff0c;还需要修改一下&#xff0c;下面记录一个已经可以在用户态代码使用的list.h /** file list.h* author PF* date 2017/05/1** port from linux kernel list.h: ht…

Linux 常用开发工具(上)(yum、vim)知识点+完整思维导图+实图例子+深入细节+通俗易懂建议收藏

绪论 耐心是一切聪明才智的基础。—— 柏拉图。本章进入到Linux下的一些常用的工具&#xff0c;这些工具能帮助我们去更好的使用Linux操作系统。 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&#xff1a;红色&#xff0c;部分为重点部分&a…