寻找最大的K个数
来源:互联网 发布:哪个电视直播软件清晰 编辑:程序博客网 时间:2024/05/17 01:28
这是一道很普遍和基础的题目,有很深的扩展性。
一、
首先,第一反应往往会想到快速排序后,再输出K个元素,但是时间复杂度为O(N*logN/log2) + O(K) = O(N*logN/log2)。
另外一种做法是,通过冒泡排序选出K个最大的出来,它的时间复杂度为O(N*K)。
这就要比较K < logN/log2 ? K : logN/log2 来取舍算法了,但是这些都不是比较好的办法。
二、
那么,进一步化简:
快排的过程是找到随机数,将它固定到最终位置,将数据按大小分组,重复递归下去,直到最后排好序,是一种分治的思想。
所以,我们可以找到第k个数的位置(由大到小排序),从而减少了查找交换次数,效率为O(N*logk/log2)。
代码如下:
void GetMaxKNumbers(int* input, int n, int* output, int k){ if(input == NULL || output == NULL || k > n || n <= 0 || k <= 0) return; int start = 0; int end = n - 1; int index = Partition(input, n, start, end); while(index != k - 1) { if(index > k - 1) { end = index - 1; index = Partition(input, n, start, end); } else { start = index + 1; index = Partition(input, n, start, end); } } for(int i = 0; i < k; ++i) output[i] = input[i];}int Partition(int data[], int length, int start, int end){ if(data == NULL || length <= 0 || start < 0 || end >= length) throw new std::exception("Invalid Parameters"); int index = RandomInRange(start, end); Swap(&data[index], &data[end]); //samll为小于end随机数的数字,index为遍历索引,当index发现比end小的数时,就与small交换,使所有small小于end int small = start - 1; for(index = start; index < end; ++ index) { if(data[index] < data[end]) { ++ small; if(small != index) Swap(&data[index], &data[small]); } } ++ small; Swap(&data[small], &data[end]); return small;}int RandomInRange(int min, int max){ int random = rand() % (max - min + 1) + min; return random;}void Swap(int* num1, int* num2){ int temp = *num1; *num1 = *num2; *num2 = temp;}
三、
但上述解法有一点不好就是会修改源数据的顺序,在工程中,往往这种情况是不允许的。
那么,我们也还可以沿用此思想,通过寻找第K大的数,并在寻找过程中标记>K的数,逼近找到K,从而最终得到结果。
其主要思想可以通过二分法查找这个K,二分法有两种:
一种是:通过min + (max - min )* 0.5 的方式,求得随机值;
二种是:通过转换为二进制,判断最高位是1 或 0 来 区分,个人推荐第二种做法。
这种方法有一点不好就是,每次遍历需将>K的数进行标记,循环时在标记的范围中进行再次筛选,而标记位会占用空间。
从时间和空间复杂度上会做到O(logN/log2),但空间上平均也会用到O(logN/log2),最坏情况下为O(N)。
四、
根据三的思想,我们可以一次遍历后,讲最大的K个数进行保存,那么怎么时刻保存这最大的K个数呢?我们可以用小根堆进行保存,将比最小根大的数加入堆,并剔除最小根,从而始终保持堆为当前遍历时,最大K个。
其代码如下:
typedef multiset<int, greater<int> > intSet;typedef multiset<int, greater<int> >::iterator setIterator;void GetLeastNumbers_Solution2(const vector<int>& data, intSet& leastNumbers, int k){ leastNumbers.clear(); if(k < 1 || data.size() < k) return; vector<int>::const_iterator iter = data.begin(); for(; iter != data.end(); ++ iter) { if((leastNumbers.size()) < k) leastNumbers.insert(*iter); else { setIterator iterGreatest = leastNumbers.begin(); if(*iter < *(leastNumbers.begin())) { leastNumbers.erase(iterGreatest); leastNumbers.insert(*iter); } } }}
由于只扫描所有数据一次,时间复杂度为O(N*logK/log2)。
可以说,在内存可容纳K时是完美的算法。
五、
可以用基数排序的思想来统计这K个数,但是这里有些条件限制:
1、数的取值范围不能太大,不然很容易达到O(N)的空间复杂度。
2、由于需要方便对相应数字次数进行查找统计,最好用到hash函数,为避免冲突,往往hash函数空间开销都会较大。
六、
编程之美2.5中,有一些扩展问题,下面谈一下我的看法:
1、对于浮点数的寻找其实可以借鉴五中的思想。首先要根据整数部分进行hash,对大于K的整数部分的数开始统计小数部分,最终达到K个数。
2、寻找k、m,实质上是在寻找第k个小的数和第m个大的数,然后在其区间确定数值,可用方法一、方法三统计。
3、可以维护一个链表和阀值,阀值为当前K个数中最小的值,网页权值变化后,大于阀值时,替换最小的数,并更新阀值。
4、可以讲每个机器用于根据不同关键词查找出最好的K个文档(允许精确度在90%),然后再有个主调度机器,判断从110%K中选取90%综合相关的作为最终的K个网页提交。
5、对于相关的查询词,其相关文档自然是有关联的,其中通过机器学习方法,获取查询词的相关度,它跟最终K个网页数中他们所占的比例成正比,从而反映,用户对关键词的强调程度。
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的k个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的k个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的K个数
- 寻找最大的k个数
- SSL/TLS协议运行机制的概述
- Unity3D中实现动态加载Resources目录外的资源
- Linux内核源码分析--内核启动命令行的传递过程(Linux-3.0 ARMv7)
- Ubuntu11.10搭建arm-linux-gcc-4.6.1交叉编译环境 .
- iOS中数组遍历的方法及比较
- 寻找最大的K个数
- 百度Android语音识别SDK语义理解与解析方法
- AIX 操作系统查看文件夹及文件大小的命令
- CMake 简明教程(3)---安装及测试
- OJ系统读入数据流的方法总结C++
- TLB
- C# Nullable的类型转换问题
- nginx反向代理与nat123转发代理的区别和二者安装使用方法和优化配置
- JS HOOK