【C++】topk问题

2023-09-19 10:17:20

解决topK问题是寻找给定数据集中前K个最大或最小的元素。

常见有三种算法:

  1. 堆排序
  • 维护一个大小为K的最小(或最大)堆。 遍历数据集,将元素插入堆中,如果堆大小超过K,则删除堆顶元素。
  • 遍历结束后,堆中剩余的K个元素就是前K个最小(或最大)的元素。 时间复杂度:O(NlogK),其中N为数据集大小。
    示例代码如下:
#include <iostream>
#include <vector>
#include <queue>

std::vector<int> topKHeap(std::vector<int>& nums, int k) {
    std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

    for (int num : nums) {
        minHeap.push(num);
        if (minHeap.size() > k) {
            minHeap.pop();
        }
    }

    std::vector<int> topk;
    while (!minHeap.empty()) {
        topk.push_back(minHeap.top());
        minHeap.pop();
    }

    return topk;
}

int main() {
    std::vector<int> nums = { 3, 2, 5, 2, 3, 2, 5, 5, 6 };
    int k = 3;

    std::vector<int> result = topKHeap(nums, k);

    std::cout << "前 " << k << " 个最大的数字为:";
    for (int num : result) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

输出结果

3 个最大的数字为:5 5 6
  1. 快速排序
  • 选择一个pivot元素将数据集划分为两部分:小于pivot的元素和大于pivot的元素。
  • 当pivot的位置恰好在K-1时,那么pivot左边的元素就是前K个最小(或右边的元素是前K个最大)的元素。
  • 如果pivot的位置小于K-1,继续在pivot右边的部分进行快速选择。
  • 如果pivot的位置大于K-1,继续在pivot左边的部分进行快速选择。
  • 时间复杂度:平均情况下为O(N),最坏情况下为O(N^2)。
    这里给出参考文章地址 添加链接描述
    在这里插入图片描述
#include <iostream>
#include <vector>
#include <algorithm>

int partition(std::vector<int>& nums, int low, int high) {
    int pivot = nums[high];
    int i = low - 1;

    for (int j = low; j < high; j++) {
        if (nums[j] < pivot) {
            i++;
            std::swap(nums[i], nums[j]);
        }
    }

    std::swap(nums[i + 1], nums[high]);

    return i + 1;
}

void quickSelect(std::vector<int>& nums, int low, int high, int k) {
    if (low < high) {
        int pivotIndex = partition(nums, low, high);

        if (pivotIndex == k - 1) {
            return;
        }
        else if (pivotIndex > k - 1) {
            quickSelect(nums, low, pivotIndex - 1, k);
        }
        else {
            quickSelect(nums, pivotIndex + 1, high, k);
        }
    }
}

std::vector<int> topKQuickSelect(std::vector<int>& nums, int k) {
    quickSelect(nums, 0, nums.size() - 1, k);

    std::vector<int> topk(nums.begin(), nums.begin() + k);

    return topk;
}

int main() {
    std::vector<int> nums = { 3, 2, 5, 2, 3, 2, 5, 5, 6 };
    int k = 3;

    std::vector<int> result = topKQuickSelect(nums, k);

    std::cout << "前 " << k << " 个最小的数字为:";
    for (int num : result) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

结果为:

3 个最小的数字为:2 2 2
  1. 计数排序
    计数排序(Counting Sort)是一种线性时间复杂度的排序算法,适用于输入数据范围较小的情况。解决topk问题可以使用计数排序的思想,具体步骤如下:

统计每个元素出现的次数:创建一个计数数组count,长度为输入数据范围的上限+1,初始值都为0。遍历输入数据,将每个元素出现的次数存储在相应的计数数组位置上。

累加计数数组:对计数数组进行累加操作,即将每个位置的值加上前一个位置的值。这一步的目的是确定每个元素在排序后的数组中的位置。

创建结果数组:创建一个与输入数组相同长度的结果数组result。

遍历输入数组并排序:从后向前遍历输入数组,根据输入元素在计数数组中的值,确定元素在结果数组中的位置,并将元素放入结果数组中。

返回结果:返回结果数组中的前k个元素。

#include <iostream>
#include <vector>
#include <algorithm>

std::vector<int> topK(std::vector<int>& nums, int k) {
    int maxNum = *max_element(nums.begin(), nums.end());
    std::vector<int> count(maxNum + 1, 0);

    // 统计每个数字的出现次数
    for (int num : nums) {
        count[num]++;
    }

    // 找到出现次数最多的前 k 个数字
    std::vector<int> topk;
    for (int i = 0; i < k; i++) {
        auto maxIt = std::max_element(count.begin(), count.end());
        int maxIndex = std::distance(count.begin(), maxIt);
        topk.push_back(maxIndex);
        *maxIt = 0; // 将当前最大值置为0,避免重复选择
    }

    return topk;
}

int main() {
    std::vector<int> nums = { 3, 2, 5, 2, 3, 2, 5, 5, 6 };
    int k = 3;

    std::vector<int> result = topK(nums, k);

    std::cout << "出现次数前 " << k << " 的数字为:";
    for (int num : result) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

出现次数前 3 的数字为:2 5 3 
更多推荐

Linux线程

1.进程是资源管理的最小单位,线程是程序执行的最小单位。2.每个进程有自己的数据段、代码段和堆栈段。线程通常叫做轻型的进程,它包含独立的栈和CPU寄存器状态,线程是进程的一条执行路径,每个线程共享其所附属进程的所有资源,包括打开的文件、内存页面、信号标识及动态分配的内存等。3.因为线程和进程比起来很小,所以相对来说,线

Java抽象类和普通类区别、 数组跟List的区别

抽象类Java中的抽象类是一种特殊的类,它不能被实例化,只能被继承。抽象类通常用于定义一些通用的属性和方法,但是这些方法的具体实现需要在子类中完成。抽象类中可以包含抽象方法和非抽象方法。抽象方法是一种没有实现的方法,只有方法的声明,没有方法体。抽象方法必须在抽象类中声明,而且子类必须实现这些抽象方法。如果子类没有实现抽

Linux sed

1.sed介绍sed:StreamEditor,流编辑器、行编辑器、逐行编辑sed将每行内容读入到“内存”中,在内存中进行处理,将结果返回给屏幕,此段内存空间称为模式空间。sed默认不编辑原文件,仅对模式空间的数据进行处理,处理结束后,将模式空间的内容显示到屏幕2.sed语法sed命令的语法格式sed[option]s

2023研究生数学建模竞赛A题B题C题D题E题F题思路+模型+代码

目录1.A题B题C题D题E题F题思路模型:9.22早上比赛开始后,第一时间更新,获取见文末名片2.竞赛注意事项:包括比赛流程,任务分配,时间把控,论文润色,已经发布在文末名片中3.常用国赛数学建模算法3.1分类问题3.2优化问题4.获取赛题思路模型见此名片1.A题B题C题D题E题F题思路模型:9.22早上比赛开始后,第

【Java】泛型 之 使用泛型

使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object://编译器警告:Listlist=newArrayList();list.add("Hello");list.add("World");Stringfirst=(String)list.get(0);Stringsecond=(String

MySQL 锁机制

文章目录MySQL锁机制1、什么是锁2、锁的缺点3、锁的分类3.1表级锁(1)什么是表级锁(2)读锁(3)写锁(4)读锁和写锁的总结(5)表级锁的优点和缺点(6)表级锁优缺点总结3.2行级锁(1)什么是行级锁(2)读锁`S`(3)写锁(4)意向共享锁`IS`(5)意向排他锁`IX`(6)行级锁的优点和缺点(7)行级锁的

【MySQL】索引

索引索引是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查询算法,这种数据结构就是索引。优缺点:优点:提高数据检索效率,降低数据库的IO成本通过索引列对数据进行排序,降低数据排序的成本,降

安卓桌面记事本便签软件哪个好用?

日常生活及工作中,很多人常常会遇到一些一闪而现的灵感,这时候拿出手机想要记录时,却找不到记录的软件。在这个快节奏的时代,安卓手机是我们日常生活不可或缺的伙伴。然而,正因为我们的生活如此忙碌,记事变得尤为重要。无论是备忘、计划、灵感还是简单的笔记,都需要一个方便而强大的工具。所以问题来了,安卓桌面记事本便签软件中哪个才是

气传导与入耳式传导区别?气传导耳机好用吗?

​入耳式耳机隔音效果好,但佩戴舒适性差,音质更偏向沉浸式。相比传统入耳式耳机,气传导耳机可以提供开放的听觉体验,音质更加自然真实,同时避免了长时间佩戴耳机可能会带来的不适感。以下是我总结了最好用的几款气传导耳机,看看有没有喜欢的。Top1:NANK南卡00压开放式耳机点评:开放式音频“技术之王”,音质与舒适最好的开放式

链表oj题1(Leetcode)——移除链表元素,反转链表,链表的中间节点,

链表OJ一,移除链表元素1.1分析1.2代码二,找到链表的中间节点2.1分析2.2代码三,反转链表3.1分析3.2代码四,找到链表中倒数第k个节点4.1分析4.2代码一,移除链表元素移除链表元素1.1分析这里的删除要分成两种情况来考虑,因为这个题目给了我们头节点,所以分成头删和非头删。因为要记录下一个节点的位置,所以1

ceph分布式存储

目录前言一、概述(一)、特点(二)、组件(三)、架构图二、搭建(一)、基础环境(二)、准备工作(三)、ceph安装(四)、集群构建(五)、dashboard安装(六)、ceph文件系统创建(七)、客户端挂载总结前言Ceph项目最早起源于Sage就读博士期间的工作(最早的成果于2004年发表),并随后贡献给开源社区。在经

热文推荐