【数据结构入门】堆的应用:TopK & 堆排序

一、Top-k问题

Top-k问题:在 N 个数中,找出前 K 个(最大/最小)的元素,一般情况下数据量 N 都远大于 k。

Top-k问题在生活中是非常的常见,比如游戏中某个大区某个英雄熟练度最高的前10个玩家的排名,我们就要根据每个玩家对该英雄的熟练度进行排序,可能有200万个玩家,但我只想选出前10个,要对所有人去排个序吗?显然没这个必要。

再比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

1.1 解法一:暴力排序

对于Top-K问题,首先想到的最简单直接的方式就是排序。

我们用堆排序,其时间复杂度为:O(N*log2N)。

但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。


1.2 解法二:建N个数的堆

建一个 N 个数的堆(C++中可用优先级队列priority_queue),不断的选数,选出前 k 个。

时间复杂度:建N个数的堆为O(N),获取堆顶元素 (也即是最值) 并删除掉堆顶元素为O(log2N),上述操作重复 k 次,所以时间复杂度为O(N+k*log2N)。

思考

能否再优化一下呢?假设 N 是 10 亿数,内存中放不下,是放在文件中的。前面两个方法都不能用了。


1.3 解法三:建K个数的堆(最优解法)

基本思路如下:

  1. 用数据集合中前K个元素来建堆。

    找前 k 个最大的元素,则建小堆

    找前 k 个最小的元素,则建大堆

  2. 用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则删除堆顶元素,再插入。

    找前 k 个最大的元素,大于堆顶元素,则删除堆顶元素,再插入

    找前 k 个最小的元素,小于堆顶元素,则删除堆顶元素,再插入

  3. 将剩余的 N-K 个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

时间复杂度

  • 建 k 个元素的堆为O(K);
  • 遍历剩余的 N-K 个元素的时间代价为O(N-K),假设运气很差,每次遍历都入堆调整;
  • 入堆调整:删除堆顶元素和插入元素都为O(log2K);
  • 所以时间复杂度为O(k + (N-K)log2K)。当 N 远大于 K 时,为O(N*log2K),这种解法更优。

假如要找出最大的前 10 个数

  • 建立 10 个元素的小堆,数据集合中前 10 个元素依次放入小堆,此时的堆顶元素是堆中最小的元素,也是堆里面第 10 个最小的元素,
  • 然后把数据集合中剩下的元素与堆顶比较,若大于堆顶则去掉堆顶,再将其插入,
  • 这样一来,堆里面存放的就是数据集合中的前 10 个最大元素,
  • 此时小堆的堆顶元素也就是堆中的第 10 个最大的元素

思考】为什么找出最大的前10个数,不能建大堆呢?
如果你建的10个元素的大堆,堆顶元素恰好是数据集合中最大的那个,那第2大的数、第3大的数就不能找不到了。

找出最大的前 10 个数代码如下

void PrintTopK(int
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值