求Top K问题

news2024/7/6 17:55:53

1.大小根堆解决Top k问题

传统思想:是将容器中的数据进行排序,排序的时间复杂度最差像冒泡是O(n^2),最好像快排是O(nlogn)。

如何在线性时间内O(n)找到Top K的元素呢?

相当于将原始序列遍历一遍就可以找到相应的元素,其实也没有必要将所有的元素进行排序,其他的元素有不有序并不关心。因此就可以使用大小根堆过滤Top K问题。

实际应用:互联网公司的智能推荐,像用户使用频率最高的一些应用,一些热点新闻,搜索频率最高的一些关键字等等

原始序列:64 45 52 80 66 68 0 2 18 75
如何求出序列中最小/大的前3个元素?

最小Top K

思想:

求最小就使用大根堆:将大根堆堆顶的大值不断淘汰,放入小值

步骤:

  • 1.先遍历序列的前K个元素,将其构建成一个大根堆

在这里插入图片描述

  • 2.不断淘汰堆顶的大值。插入小于堆顶的元素,出堆顶元素,平衡大顶堆
    在这里插入图片描述
// 求vec中值最小的前5个元素
int main()
{
    vector<int> vec;
    srand(time(NULL));
    for (int i = 0; i < 1000; i++)
    {
        vec.push_back(rand() % 10000);
    }
    // 求vec中值最小的前5个元素
    priority_queue<int> maxheap;
    int k = 5;

    // 由前k个元素构建一个大根堆
    for (int i = 0; i < 5; i++)
    {
        maxheap.push(vec[i]);
    }

    // 遍历剩余的元素直到最后
    for (int i = 5; i < vec.size(); i++)
    {
        if (maxheap.top() > vec[i])
        {
            maxheap.pop();
            maxheap.push(vec[i]);
        }
    }

    // 输出结果
    while (!maxheap.empty())
    {
        cout << maxheap.top() << " ";
        maxheap.pop();
    }
    cout << endl;
}

最大Top K

求最大就使用小根堆:将小根堆堆顶的小值不断淘汰,放入大值

步骤:

  • 1.先遍历序列的前K个元素,将其构建成一个小根堆
    在这里插入图片描述
  • 2.不断淘汰堆顶的小值。插入大于堆顶的元素,出堆顶元素,平衡小顶堆
    在这里插入图片描述
int main()
{
    vector<int> vec;
    srand(time(NULL));
    for (int i = 0; i < 1000; i++)
    {
        vec.push_back(rand() % 10000);
    }
    // 求vec中值最大的前5个元素
    priority_queue<int, vector<int>, greater<int>> minheap;
    int k = 5;

    // 由前k个元素构建一个小根堆
    for (int i = 0; i < 5; i++)
    {
        minheap.push(vec[i]);
    }

    // 遍历剩余的元素直到最后
    for (int i = 5; i < vec.size(); i++)
    {
        if (minheap.top() < vec[i])
        {
            minheap.pop();
            minheap.push(vec[i]);
        }
    }

    // 输出结果
    while (!minheap.empty())
    {
        cout << minheap.top() << " ";
        minheap.pop();
    }
    cout << endl;

}

找第K小或者第K大访问堆顶就行了

2.快排分割解决Top K

快排详解

利用快排分割函数每次返回的基准数的位置,找出前top k大的或者前top k小的数据

//

#include <iostream>
using namespace std;

// 快排分割函数
int Partation(int arr[], int begin, int end)
{
    int val = arr[begin];
    int i = begin;
    int j = end;

    while (i < j)
    {
        while(i < j&& arr[j] < val)
            j--;

        if (i < j)
        {
            arr[i] = arr[j];
            i++;
        }

        while (i < j && arr[i] > val)
            i++;

        if (i < j)
        {
            arr[j] = arr[i];
            j--;
        }
    }

    arr[i] = val;
    return i;
}

// 求top k的函数
void SelectTopK(int arr[], int begin, int end, int k)
{
    int pos = Partation(arr, begin, end);
    if (pos == k - 1)
    {
        return;
    }
    else if (pos > k - 1)
    {
        SelectTopK(arr, begin, pos - 1, k);
    }
    else
    {
        SelectTopK(arr, pos + 1, end, k);
    }
}

int main()
{
    int arr[] = { 64, 45, 52, 80, 66, 68, 0, 2, 18, 75 };
    int size = sizeof arr / sizeof arr[0];

    // 求值最小的前3个元素
    int k = 3;
    SelectTopK(arr, 0, size - 1, k);

    for (int i = 0; i < k; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}

大文件求Top K

在这里插入图片描述

3.查重+Top K(重复次数最大的Top K)

统计重复次数最小的前K个数字

思路:
由于是统计重复次数最小,并且最终输出的是元素。

  • 1.首先使用哈希表统计每个元素的重复次数,并记录
  • 2.将哈希表的前K个key-value作为pair插入堆,进行堆排(要注意自定义类型要改变比较器的比较规则)
  • 3.遍历哈希表,找出前K个元素
// 统计重复次数最小的前3个数字
int main()
{
    vector<int> vec;
    srand(time(NULL));
    for (int i = 0; i < 10000; i++)
    {
        vec.push_back(rand() % 1000);
    }

    // 统计重复次数最小的前3个数字
    int k = 3;
    unordered_map<int, int> map;
    for (auto key : vec)
    {
        map[key]++;
    }

    // 放入大根堆的时候,需要放key-value键值对
    using Type = pair<int, int>;
    using Comp = function<bool(Type&, Type&)>;
    priority_queue<Type, vector<Type>, Comp> maxheap(
        [](Type& a, Type& b)->bool {
            return a.second < b.second;//大根堆默认比较器是less,所以自定义的比较方法也应该是小于
        });

    auto it = map.begin();
    for (int i = 0; i < k; i++, ++it)
    {
        maxheap.push(*it);
    }

    for (; it != map.end(); ++it)
    {
        if (maxheap.top().second > it->second)
        {
            maxheap.pop();
            maxheap.push(*it);
        }
    }

    while (!maxheap.empty())
    {
        cout << "key:" << maxheap.top().first
            << " cnt:" << maxheap.top().second << endl;
        maxheap.pop();
    }
}

统计重复次数最大的前K个数字

int main()
{
    vector<int> vec;
    srand(time(NULL));
    for (int i = 0; i < 10000; i++)
    {
        vec.push_back(rand() % 1000);
    }

    // 统计重复次数最大的前3个数字
    int k = 3;
    unordered_map<int, int> map;
    for (auto key : vec)
    {
        map[key]++;
    }

    // 放入大根堆的时候,需要放key-value键值对
    using Type = pair<int, int>;
    using Comp = function<bool(Type&, Type&)>;
    priority_queue<Type, vector<Type>, Comp> minheap(
        [](Type& a, Type& b)->bool {
            return a.second > b.second;
        });

    auto it = map.begin();
    for (int i = 0; i < k; i++, ++it)
    {
        minheap.push(*it);
    }

    for (; it != map.end(); ++it)
    {
        if (minheap.top().second < it->second)
        {
            minheap.pop();
            minheap.push(*it);
        }
    }

    while (!minheap.empty())
    {
        cout << "key:" << minheap.top().first
            << " cnt:" << minheap.top().second << endl;
        minheap.pop();
    }
}

大文件

在这里插入图片描述

// 大文件划分小文件(哈希映射)+ 哈希统计 + 小根堆(快排也可以达到同样的时间复杂度)
int main()
{
	//通过下面的代码,先生成放整数的二进制文件:
	FILE* pf1 = nullptr;
	errno_t res =fopen_s(&pf1,"data.dat", "wb");
	for (int i = 0; i < 20000; ++i)
	{
		int data = rand();
		if (data < 0)
			cout << data << endl;
		fwrite(&data, 4, 1, pf1);//将data写入pf1,数据类型4字节,写1个
	}
	fclose(pf1);



	// 打开存储数据的原始文件
	FILE* pf = nullptr;
		errno_t res2=fopen_s(&pf,"data.dat", "rb");
	if (pf == nullptr)
		return 0;

	// 这里由于原始数据量缩小,所以这里文件划分的个数也变小了,11个小文件
	const int FILE_NO = 11;
	FILE* pfile[FILE_NO] = { nullptr };
	for (int i = 0; i < FILE_NO; ++i)
	{
		char filename[20];
		sprintf_s(filename, "data%d.dat", i + 1);
		 
			errno_t res1 = fopen_s(&pfile[i],filename, "wb+");
	}

	// 哈希映射,把大文件中的数据,映射到各个小文件当中
	int data;
	while (fread(&data, 4, 1, pf) > 0)
	{
		int findex = data % FILE_NO;
		fwrite(&data, 4, 1, pfile[findex]);
	}

	// 定义一个链式哈希表
	unordered_map<int, int> numMap;
	// 先定义一个小根堆
	using P = pair<int, int>;
	using FUNC = function<bool(P&, P&)>;
	using MinHeap = priority_queue<P, vector<P>, FUNC>;
	MinHeap minheap([](auto& a, auto& b)->bool {
		return a.second > b.second; // 自定义小根堆元素大小比较方式
		});

	// 分段求解小文件的top 10大的数字,并求出最终结果
	for (int i = 0; i < FILE_NO; ++i)
	{
		// 恢复小文件的文件指针到起始位置
		fseek(pfile[i], 0, SEEK_SET);

		while (fread(&data, 4, 1, pfile[i]) > 0)
		{
			numMap[data]++;
		}

		int k = 0;
		auto it = numMap.begin();

		// 如果堆是空的,先往堆方10个数据
		if (minheap.empty())
		{
			// 先从map表中读10个数据到小根堆中,建立top 10的小根堆,最小的元素在堆顶
			for (; it != numMap.end() && k < 10; ++it, ++k)
			{
				minheap.push(*it);
			}
		}

		// 把K+1到末尾的元素进行遍历,和堆顶元素比较
		for (; it != numMap.end(); ++it)
		{
			// 如果map表中当前元素重复次数大于,堆顶元素的重复次数,则替换
			if (it->second > minheap.top().second)
			{
				minheap.pop();
				minheap.push(*it);
			}
		}

		// 清空哈希表,进行下一个小文件的数据统计
		numMap.clear();
	}

	// 堆中剩下的就是重复次数最大的前k个
	while (!minheap.empty())
	{
		auto& pair = minheap.top();
		cout << pair.first << " : " << pair.second << endl;
		minheap.pop();
	}

	return 0;
}

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

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

相关文章

C语言之函数式宏

目录 函数和数据类型 函数式宏 函数和函数式宏 函数式宏和对象式宏 不带参数的函数式宏 函数式宏和逗号运算符 函数式宏和函数类似并且比函数更加灵活&#xff0c;下面我们就来学习函数式宏的相关内容。 函数和数据类型 我们来编写一个程序&#xff0c;它能计算出所读取…

python蓝桥杯的回形取数

#来源于蓝桥杯的训练 题号是用户登录https://www.lanqiao.cn/problems/1517/learning/?page1&first_category_id1&problem_id1517 根据题目描述可以知道&#xff0c;我们传入的是一个矩阵。 在这里我们使用列表来实现矩阵。 那么&#xff0c;我们直接看代码 dir …

超详细 | 黏菌算法原理、实现及其改进与利用(Matlab/Python)

测试函数为F15 在MATLAB中执行程序结果如下&#xff1a; 在Python中执行程序结果如下&#xff1a; 众所周知&#xff0c;麻雀搜索算法SSA是2020年由东华大学沈波教授团队提出[1]的一种性能十分优异的优化算法&#xff0c;而最近作者发现&#xff0c;在2020年还提出了一个优…

腾讯云:AI云探索之路

随着科技的飞速发展&#xff0c;人工智能(AI)云计算领域日益显现出其巨大的潜力和价值。在这个充满挑战和机遇的领域&#xff0c;腾讯云凭借其卓越的技术和创新能力&#xff0c;取得了令人瞩目的成果。本文将深入探讨腾讯云在AI云计算领域的优势&#xff0c;以及其为人工智能发…

Post Json数据与Form表单数据转换器

具体请访问&#xff1a;在线Json转Form表单参数工具

python绘制gif动图--避免收费拒绝水印

在cp源码解读-CSDN博客文章浏览阅读341次,点赞4次,收藏6次。coreutils:cp源代码分析https://blog.csdn.net/zhaiminlove/article/details/135026160中为了直观的表达文件是如何一步步的copy的,就想到了做一个gif,但是从来没弄过,一下有点麻爪了。 首先想到的是百度、Goog…

孩子都能学会的FPGA:第三十三课——用FPGA实现一个通用的SPI主机接收模块

&#xff08;原创声明&#xff1a;该文是作者的原创&#xff0c;面向对象是FPGA入门者&#xff0c;后续会有进阶的高级教程。宗旨是让每个想做FPGA的人轻松入门&#xff0c;作者不光让大家知其然&#xff0c;还要让大家知其所以然&#xff01;每个工程作者都搭建了全自动化的仿…

C语言指针4

1. #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h>int main() {int a 10;int* p &a;//一级指针int** pp &p;//二级指针return 0; }上述代码中p的类型是int* pp的类型是int** 2.int* arr[5]; 数组arr的每个元素是整形指针 3.定义一个变量时,去掉变…

报数游戏C语言

分析:掌握数字移动的规律&#xff0c;以及判断&#xff0c;我们可以用一个二维数组来记录每一个人说的数字&#xff0c;就像第一张图片一样&#xff0c;西安向右边移动&#xff0c;再向左下移动&#xff0c;再向左边移动&#xff0c;在向右边移动&#xff0c;在可以用一个数组来…

3 - Electron BrowserWindow对象 关于窗口

优雅的打开应用~ 当加载缓慢&#xff0c;打开应用的一瞬间会出现白屏&#xff0c;以下方法可以解决 const mainWindow new BrowserWindow({ show: false }) mainWindow.once(ready-to-show, () > {mainWindow.show() }) 设置背景颜色 const win new BrowserWindow({ b…

比特币即自由

号外&#xff1a;教链内参12.15《疯狂的铭文》 文 | Ross Ulbricht. 原文标题&#xff1a;Bitcoin Equals Freedom. 2019.9.25 在中本聪发明比特币后的头一年左右&#xff0c;发生了一些特别的事情&#xff0c;不仅没有人预料到&#xff0c;甚至很多人认为不可能。试着想象一下…

【Python从入门到进阶】44、Scrapy的基本介绍和安装

接上篇《43.验证码识别工具结合requests的使用》 上一篇我们学习了如何使用验证码识别工具进行登录验证的自动识别。本篇我们开启一个新的章节&#xff0c;来学习一下快速、高层次的屏幕抓取和web抓取框架Scrapy。 一、Scrapy框架的背景和特点 Scrapy框架是一个为了爬取网站数…

Python:Jupyter

Jupyter是一个开源的交互式计算环境&#xff0c;由Fernando Perez和Brian Granger于2014年创立。它提供了一种方便的方式来展示、共享和探索数据&#xff0c;并且可以与多种编程语言和数据格式进行交互。Jupyter的历史可以追溯到2001年&#xff0c;当时Fernando Perez正在使用P…

开源免费图床Lychee本地部署搭建个人云图床并公网访问【内网穿透】

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

LeetCode(64)分隔链表【链表】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 分隔链表 1.题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示…

加油站“变身”快充站,探讨充电新模式——安科瑞 顾烊宇

摘要&#xff1a;新能源汽车规模化发展的同时&#xff0c;充电不便利的痛点愈发明显。在未来的新能源汽车行业发展当中&#xff0c;充电的矛盾要远远大于造车的矛盾&#xff0c;解决好充电的问题成为电动汽车行业发展的一个突出问题。解决充电补能问题&#xff0c;重要的方式之…

Python实现员工管理系统(Django页面版 ) 六

本篇博客主要实现用户账号管理&#xff0c;这与之前的账号管理不同&#xff0c;之前的账号管理你可以理解为公司在外面买的一些手机号然后需要发放给员工做内部使用&#xff0c;而本篇博客的用户账号管理主要是为了后续的登录网页实现&#xff0c;那么我们开始今天的项目实现吧…

栈和队列的实现(Java篇)

文章目录 一、栈的概念二、栈的实现2.1压栈(push)2.2出栈(pop)2.3获取栈顶元素(peek)2.4判断栈是否为空(isEmpty)栈的实现测试 三、队列的概念四、队列的实现4.1入队(offer)4.2出队(poll)4.3判断队列是否为空4.4获取对头元素队列的实现测试 五、循环队列5.1入队5.2出队5.3获取队…

手把手教你Linux查找Java的安装目录并设置环境变量以及Linux下执行javac未找到命令的保姆级教学

查找Java的安装目录 输入 java -version&#xff0c;查看是否成功安装Java 输入 which java&#xff0c;查看Java的执行路径 输入 ls -lrt /usr/bin/java 输入 ls -lrt /etc/alternatives/java&#xff0c;/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-0.el7_7.x86_64 就是J…

系列七、函数

一、函数 1.1、概述 函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码MySQL中已经为我们提供好了&#xff0c;我们要做的就是在合适的业务场景调用对应的函数完成相应的业务需求即可。 1.2、分类 按照业务分类&#xff0c;MySQL中…