前k个高频元素-堆排序

背景:很多时候需要从一串数据流中记录下前k个最大,或者最小的元素。这个问题的关键是由于用户无法得知数据流的具体长度,且不会有足够的长度来缓存所有的数据。因此无法用排序的方法得到数据流中的前k个最大的元素。

此外,并不要求这k个数据有完整的排序关系。因此可以使用堆过滤的方法。

堆数据结构特征和存储方式已在STL容器篇中说明过,此处直接使用。

问题:假定一个非空的整数数组,返回其中出现频率前 k 高的元素。假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。

分析:这个问题需要分两个步骤来解决,1.统计数组中所有元素的频次。2.获取频次为前k的数据。统计频次已经有很多经验了,使用unordered_map结构可以很快的统计频次信息,则关键的问题是如何得到前k的元素(这里我避免使用了“排序”的词语,就是为了强调不严格排序)。使用堆(heap)可以从所有的元素中过滤(堆更像是过滤的操作,把不符合的都筛出去,得到前面的)出前k的元素。为了简便,可以使用preority_queue来实现过滤  (因为优先队列的底层容器就是堆,当然,也可以自己设计堆)。

//一个排序仿函数,针对map中的元素设计的,且需要声明在前面
struct compare{
bool operator()(pair<int, int> &lhs, pair<int, int> &rhs){return lhs.second>rhs.second;}
};

vector<int> topKFrequents(vector<int> &nums, int k){

vector<int> res;
unordered_map<int, int> map;
int i=0;
while(i<nums.size()){
map[nums[i++]]++;//初学者注意这个写法,等价于 map[nums[i]]++;i++;
}
//if(map.size()<=k) {for(auto it=map.begin();it!=map.end();++it){res.push_back((*it).first;)}}
//由于假设nums中元素的类别总是大于k的,因此上面注释掉了

//STL容器库中priority_queue默认为大顶堆,这里需要使用小顶堆,
priority_queue<pair<int, int>, vector<pair<int, int>>, compare> minq;
for(auto it=map.begin();it!=map.end();++it){
minq.push(*it);
if(minq.size()>k){minq.pop();}
}

while(!pri_que.empty()){res.push_back(minq.top().first);minq.pop();}
return res;

}

这里展示了priority_queue这一数据结构的应用,有一些注意的点:1.priority_queue默认是大顶堆,这里使用了小顶堆,原因是优先队列的pop()函数删除的是队首的元素,为了保存前k个频率最高的元素,只能将频率低的元素放在堆顶,删除。2.由于进队列的元素是pair<int, int>类型的,因此自定义了一个仿函数用来排序。通常,对于自定义的数据类型,都需要由用户自己来定义仿函数。

另注:JZOffer中有过一个用priority_queue求解的例子,问题:从用户输入的数据流中得到最大的k个数。注意,用户随时可以输入,因此你无法得知数据流有多长,也没有足够的内存来缓存这些数据。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值