Copyright © 1900-2016, NORYES, All Rights Reserved.

http://www.cnblogs.com/noryes/

欢迎转载,请保留此版权声明。

-----------------------------------------------------------------------------------------

 

转载自http://www.cnblogs.com/luxiaoxun/archive/2012/08/06/2624799.html

寻找N个数中最大的K个数,本质上就是寻找最大的K个数中最小的那个,也就是第K大的数。

可以使用二分搜索的策略来寻找N个数中的第K大的数。对于一个给定的数p,可以在O(N)的时间复杂度内找出所有不小于p的数。

寻找第k大的元素:

#include <iostream>using namespace std;
//快速排序的划分函数
int partition(int a[],int l,int r)
{
int i,j,x,temp;
i = l;
j = r+1;
x = a[l];
//将>=x的元素换到左边区域
//将<=x的元素换到右边区域
while (1)
{
while(a[++i] > x);
while(a[--j] < x);
if(i >= j) break;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
a[l] = a[j];
a[j] = x;
return j;
} //随机划分函数
int random_partition(int a[],int l,int r)
{
int i = l+rand()%(r-l+1);//生产随机数
int temp = a[i];
a[i] = a[l];
a[l] = temp;
return partition(a,l,r);//调用划分函数
} //线性寻找第k大的数
int random_select(int a[],int l,int r,int k)
{
int i,j;
if (l == r) //递归结束
{
return a[l];
}
i = random_partition(a,l,r);//划分
j = i-l+1;
if(k == j) //递归结束,找到第K大的数
return a[i];
if(k < j)
{
return random_select(a,l,i-1,k);//递归调用,在前面部分查找第K大的数
}
else
return random_select(a,i+1,r,k-j);//递归调用,在后面部分查找第K大的数
} int main()
{
int a[]={1,2,3,4,6,6,7,8,10,10}; cout<<random_select(a,0,9,1)<<endl;
cout<<random_select(a,0,9,5)<<endl;
return 0;
}

如果所有N个数都是正整数,且它们的取值范围不太大,可以考虑申请空间,记录每个整数出现的次数,然后再从大到小取最大的K个。比如,所有整数都在(0, MAXN)区间中的话,利用一个数组count[MAXN]来记录每个整数出现的个数(count[i]表示整数i在所有整数中出现的个数)。只需要扫描一遍就可以得到count数组。然后,寻找第K大的元素:

for(sumCount = 0, v = MAXN-1; v >= 0; v--)
{
sumCount += count[v];
if(sumCount >= K)
break;
}
return v;

极端情况下,如果N个整数各不相同,我们甚至只需要一个bit来存储这个整数是否存在(bit位为1或为0),这样使用的空间可以大大压缩。

当然也可以使用像计数排序、桶排序等这些以O(N)的时间排序算法也可以寻找第K大的数,但这也是以空间换时间为代价的。

实际情况下,并不一定保证所有元素都是正整数,且取值范围不太大。上面的方法仍然可以推广使用。如果N个数中最大的数Vmax,最小的Vmin,我们可以把这个区间[Vmax,Vmin]分成M块,每个小区间的跨度为d=(Vmax-Vmin)/M,即[Vmin,Vmin+d],[Vmin+d,Vmin+2d]......然后,扫描一遍所有元素,统计各个小区间中的元素个数,就可以知道第K大的元素在哪一个小区间。然后,再在那个小区间中找第K大的数(此时这个小区间中,第K大的数可能就是第T大的数了,这个T和每个小区间的个数有关)。我们需要找一个尽量大的M,但M的取值受到内存的限制。

解法一:该解法是大部分能想到的,也是第一想到的方法。假设数据量不大,可以先用快速排序或堆排序,他们的平均时间复杂度为O(N*logN),然后取出前K个,时间复杂度为O(K),总的时间复杂度为O(N*logN)+O(K).
        当K=1时,上面的算法的时间复杂度也是O(N*logN),上面的算法是把整个数组都进行了排序,而原题目只要求最大的K个数,并不需要前K个数有限,也不需要后N-K个数有序。可以通过部分排序算法如选择排序和交换排序,把N个数中的前K个数排序出来,复杂度为O(N*K),选择哪一个,取决于K的大小,在K(K<logN)较小的情况下,选择部分排序。

解法二:(掌握)避免对前K个数进行排序来获取更好的性能(利用快速排序的原理)。
        假设N个数存储在数组S中,从数组中随机找一个元素X,将数组分成两部分Sa和Sb.Sa中的元素大于等于X,Sb中的元素小于X。
    出现如下两种情况:
   (1)若Sa组的个数大于或等于K,则继续在sa分组中找取最大的K个数字 。
   (2)若Sa组中的数字小于K ,其个数为T,则继续在sb中找取 K-T个数字 。
   一直这样递归下去,不断把问题分解成小问题,平均时间复杂度为O(N*logK)。
   代码如下:

  1. /*将数组a[s]...a[t]中的元素用一个元素划开,保存中a[k]中*/
  2. void partition(int a[], int s,int t,int &k)
  3. {
  4. int i,j,x;
  5. x=a[s];    //取划分元素
  6. i=s;        //扫描指针初值
  7. j=t;
  8. do
  9. {
  10. while((a[j]<x)&&i<j) j--;   //从右向左扫描,如果是比划分元素小,则不动
  11. if(i<j) a[i++]=a[j];           //大元素向左边移
  12. while((a[i]>=x)&&i<j) i++;      //从左向右扫描,如果是比划分元素大,则不动
  13. if(i<j) a[j--]=a[i];            //小元素向右边移
  14. }while(i<j); //直到指针i与j相等
  15. a[i]=x;      //划分元素就位
  16. k=i;
  17. }
  18. /*查找数组前K个最大的元素,index:返回数组中最大元素中第K个元素的下标(从0开始编号),high为数组最大下标*/
  19. int FindKMax(int a[],int low,int high,int k)
  20. {
  21. int q;
  22. int index=-1;
  23. if(low < high)
  24. {
  25. partition(a , low , high,q);
  26. int len = q - low + 1; //表示第几个位置
  27. if(len == k)
  28. index=q; //返回第k个位置
  29. else if(len < k)
  30. index= FindKMax(a , q + 1 , high , k-len);
  31. else
  32. index=FindKMax(a , low , q - 1, k);
  33. }
  34. return index;
  35. }
  36. int main()
  37. {
  38. int a[]={20,100,4,2,87,9,8,5,46,26};
  39. int Len=sizeof(a)/sizeof(int);
  40. int K=4;
  41. FindKMax(a , 0 , Len- 1 , K) ;
  42. for(int i = 0 ; i < K ; i++)
  43. cout<<a[i]<<" ";
  44. return 0;
  45. }

 解法三:(掌握)用容量为K的最小堆来存储最大的K个数。最小堆的堆顶元素就是最大K个数中的最小的一个。每次扫描一个数据X,如果X比堆顶元素Y小,则不需要改变原来的堆。如果X比堆顶元素大,那么用X替换堆顶元素Y,在替换之后,X可能破坏了最小堆的结构,需要调整堆来维持堆的性质。调整过程时间复杂度为O(logK)。 全部的时间复杂度为O(N*logK)。
          这种方法当数据量比较大的时候,比较方便。因为对所有的数据只会遍历一次,第一种方法则会多次遍历数组。 如果所查找的K的数量比较大。可以考虑先求出k` ,然后再求出看k`+1 到 2 * k`之间的数据,然后一次求取。
  
  代码如下:

  1. void heapifymin(int Array[],int i,int size)
  2. {
  3. if(i<size)
  4. {
  5. int left=2*i+1;
  6. int right=2*i+2;
  7. int smallest=i;//假设最小的节点为父结点
  8. //确定三个结点中的最大结点
  9. if(left<size)
  10. {
  11. if(Array[smallest]>Array[left])
  12. smallest=left;
  13. }
  14. if(right<size)
  15. {
  16. if(Array[smallest]>Array[right])
  17. smallest=right;
  18. }
  19. //开始交换父结点和最大的子结点
  20. if(smallest!=i)
  21. {
  22. int temp=Array[smallest];
  23. Array[smallest]=Array[i];
  24. Array[i]=temp;
  25. heapifymin(Array,smallest,size);//对调整的结点做同样的交换
  26. }
  27. }
  28. }
  29. //建堆过程,建立最小堆,从最后一个结点开始调整为最小堆
  30. void min_heapify(int Array[],int size)
  31. {
  32. int i;
  33. for(i=size-1;i>=0;i--)
  34. heapifymin(Array,i,size);
  35. }
  36. //k为需要查找的最大元素个数,size为数组大小,kMax存储k个元素的最小堆
  37. void FindMax(int Array[],int k,int size,int kMax[])
  38. {
  39. for(int i=0;i<k;i++)
  40. kMax[i]=Array[i];
  41. //对kMax中的元素建立最小堆
  42. min_heapify(kMax,k);
  43. printf("最小堆如下所示 : \n");
  44. for(i=0;i<k;i++)
  45. printf("%4d",kMax[i]);
  46. printf("\n");
  47. for(int j=k;j<size;j++)
  48. {
  49. if(Array[j]>kMax[0]) //如果最小堆的堆顶元素,替换
  50. {
  51. int temp=kMax[0];
  52. kMax[0]=Array[j];
  53. Array[j]=temp;
  54. //可能破坏堆结构,调整kMax堆
  55. min_heapify(kMax,k);
  56. }
  57. }
  58. }
  59. int main()
  60. {
  61. int a[]={10,23,8,2,52,35,7,1,12};
  62. int length=sizeof(a)/sizeof(int);
  63. //最大四个元素为23,52,35,12
  64. /***************查找数组中前K个最大的元素****************/
  65. int k=4;
  66. int * kMax=(int *)malloc(k*sizeof(int));
  67. FindMax(a,k,length,kMax);
  68. printf("最大的%d个元素如下所示 : \n",k);
  69. for(int i=0;i<k;i++)
  70. printf("%4d",kMax[i]);
  71. printf("\n");
  72. return 0;
  73. }

 解法四:这也是寻找N个数中的第K大的数算法。利用二分的方法求取TOP k问题。 首先查找 max 和 min,然后计算出mid = (max + min) / 2该算法的实质是寻找最大的K个数中最小的一个。

  1. const int N = 8 ;
  2. const int K = 4 ;
  3. /*
  4. 利用二分的方法求取TOP k问题。
  5. 首先查找 max 和 min,然后计算出 mid = (max + min) / 2
  6. 该算法的实质是寻找最大的K个数中最小的一个。
  7. */
  8. int find(int * a , int x) //查询出大于或者等于x的元素个数
  9. {
  10. int sum = 0 ;
  11. for(int i = 0 ; i < N ; i++ )
  12. {
  13. if(a[i] >= x)
  14. sum++ ;
  15. }
  16. return sum ;
  17. }
  18. int getK(int * a , int max , int min) //最终max min之间只会存在一个或者多个相同的数字
  19. {
  20. while(max - min > 1)             //max - min的值应该保证比两个最小的元素之差要小
  21. {
  22. int mid = (max + min) / 2 ;
  23. int num = find(a , mid) ;    //返回比mid大的数字个数
  24. if(num >= K)                 //最大的k个数目都要比min值大
  25. min = mid ;
  26. else
  27. max = mid  ;
  28. }
  29. cout<<"end"<<endl;
  30. return min ;
  31. }
  32. int main()
  33. {
  34. int a[N] = {54, 2 ,5 ,11 ,554 ,65 ,33 ,2} ;
  35. int x = getK(a , 554 , 2) ;
  36. cout<<x<<endl ;
  37. getchar() ;
  38. return 0 ;
  39. }

该算法在实际应用中效果不佳。

          解法五:如果N个数都是正数,取值范围不太大,可以考虑用空间换时间。申请一个包括N中最大值的MAXN大小的数组count[MAXN],count[i]表示整数i在所有整数中的个数。这样只要扫描一遍数组,就可以得到第K大的元素。
代码如下:

  1. for(sumCount = 0, v = MAXN -1; v >=0; v--)
  2. {
  3. cumCount += count[v];
  4. if(sumCount >= k)
  5. break;
  6. }
  7. return v;

        扩展问题:
1.如果需要找出N个数中最大的K个不同的浮点数呢?比如,含有10个浮点数的数组(1.5,1.5,2.5,3.5,3.5,5,0,- 1.5,3.5)中最大的3个不同的浮点数是(5,3.5,2.5)。
     解答:除了解法五不行,其他的都可以。因为最后一种需要是正数。
2. 如果是找第k到第m(0<k<=m<=n)大的数呢?
       解答:可以用小根堆来先求出m个最大的,然后从中输出k到m个。
3. 在搜索引擎中,网络上的每个网页都有“权威性”权重,如page rank。如果我们需要寻找权重最大的K个网页,而网页的权重会不断地更新,那么算法要如何变动以达到快速更新(incremental update)并及时返回权重最大的K个网页?
   解答:(解法三)用堆排序当每一个网页权重更新的时候,更新堆。

举一反三:查找最小的K个元素

解答:最直观的方法是用快速排序或堆排序先排好,在取前K小的数据。最好的办法是利用解法二解法三的原理进行查找。

作者:阿凡卢
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

算法系列:寻找最大的 K 个数的更多相关文章

  1. 算法练习-寻找最小的k个数

    练习问题来源 https://wizardforcel.gitbooks.io/the-art-of-programming-by-july/content/02.01.html 要求 输入n个整数, ...

  2. 算法练习:寻找最小的k个数

    参考July的文章:http://blog.csdn.net/v_JULY_v/article/details/6370650 寻找最小的k个数题目描述:查找最小的k个元素题目:输入n个整数,输出其中 ...

  3. 第2章 数字之魅——寻找最大的K个数

    寻找最大的K个数 问题描述 在面试中,有下面的问答: 问:有很多个无序的数,我们姑且假定它们各不相等,怎么选出其中最大的若干个数呢? 答:可以这样写:int array[100] …… 问:好,如果有 ...

  4. O(N)的时间寻找最大的K个数

    (转:http://www.cnblogs.com/luxiaoxun/archive/2012/08/06/2624799.html) 寻找N个数中最大的K个数,本质上就是寻找最大的K个数中最小的那 ...

  5. 03寻找最小的k个数

    题目描述:查找最小的k个元素         题目:输入n个整数,输出其中最小的k个.         例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4. 1:最简单 ...

  6. 编程之美2.5:寻找最大的K个数

    编程之美2.5:寻找最大的K个数 引申:寻找第k大的数: 方法一: // 选择第k大的数(通过改进快速排序来实现) public static void SelectShort(int[] array ...

  7. 算法笔记_035:寻找最小的k个数(Java)

    目录 1 问题描述 2 解决方案 2.1 全部排序法 2.2 部分排序法 2.3 用堆代替数组法 2.4线性选择算法   1 问题描述 有n个整数,请找出其中最小的k个数,要求时间复杂度尽可能低. 2 ...

  8. 编程之法:面试和算法心得(寻找最小的k个数)

    内容全部来自编程之法:面试和算法心得一书,实现是自己写的使用的是java 题目描述 输入n个整数,输出其中最小的k个. 分析与解法 解法一 要求一个序列中最小的k个数,按照惯有的思维方式,则是先对这个 ...

  9. 寻找最大的k个数问题

    这是编程之美书第2.5节的一道题目. 各种解法: 解法一,用nlgn复杂度的排序算法对数组进行从大到小排序,取前K个.但这方法做了两件不必要做的事:它对想得到的K个数进行了排序,对不想得到的n-K个数 ...

随机推荐

  1. deepin 15.3 安装数据库MariaDB10.0

    直接终端执行安装命令即可 sudo apt-get install mariadb-server -y 查询DB编码 #进入数据库 mysql -uroot -p #显示默认编码 show varia ...

  2. Thinking in java学习笔记之interface

    (完全解耦)策略设计模式:

  3. CocoaPods第三方库管理 iOS

    越来越多的SVN管理,越来越多的工程文件,我不能总是那么一个一个的把第三方库拖进去,我厌倦了拖拽和配置,我找到了替代方法--CocoaPods 补充一下:最近在给新机子安装时 发现 sudo gem ...

  4. 【整理】认识MSG结构体

    在Windows程序中,消息是由MSG结构体来表示的.MSG结构体的定义如下(参见MSDN): typedef struct tagMSG { HWND hwnd; UINT message; WPA ...

  5. 增量关联规则挖掘—FUP算法

    一.背景介绍 关联规则( Association rule)概念最初由Agrawal提出,是数据挖掘的一个重要研究领域, 其目的是发现数据集中有用的频繁模式. 静态关联规则挖掘,是在固定数据集和支持度 ...

  6. Python脚本报错AttributeError: ‘module’ object has no attribute’xxx’解决方法

    最近在编写Python脚本过程中遇到一个问题比较奇怪:Python脚本完全正常没问题,但执行总报错"AttributeError: 'module' object has no attrib ...

  7. 面试题目——《CC150》线程与锁

    package cc150.thread_lock; public class RunnableThreadExample implements Runnable{ public int count ...

  8. 关于学习angularJS 的 心里路程(二)

    这一次主要的学习内容是 ng-route(本次的项目由于种种原因吧,我们采用了ui-router,而不是原生的ng-route) * 配置路由. * 注意这里采用的是ui-router这个路由,而不是 ...

  9. visio二次开发——图纸解析

    (转发请注明来源:http://www.cnblogs.com/EminemJK/) visio二次开发的案例或者教程,国内真的非常少,这个项目也是花了不少时间来研究visio的相关知识,困难之所以难 ...

  10. 在Activity之间传递参数(二)

    传递数据包bundle: 1.MainActivity.class: findViewById(R.id.btnStartAty).setOnClickListener(new View.OnClic ...