寻找最大的K个数

Posted xuguoli_beyondboy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了寻找最大的K个数相关的知识,希望对你有一定的参考价值。

编程之美有一道考察多种排序的题目,题目如下:
有一个长度为N的无序数组,假定其中的每一个元素都各不相等,求其中最大的K个数。
作者对于此题目结合各种排序算法给出了五种解法思路。
解法一:
使用快速排序或堆排序对它们元素进行排序,整个排序的时间复杂度为O(N* log2 N),然后取出前K个,时间复杂度为O(K),总时间复杂度O(N* log2 N)+O(K)=O(N* log2 N).根据题目要求,并不需要后N-KN-K个数有序,而只需要寻找最大K的个数,因此可以用选择排序和交换排序进行部分排序来筛选最大的K个数,其时间复杂度为O(N*K).
解法二:
利用快速排序思想对数据进行分组,让其一组数据的任何一个数都比另一组中的任意数大,整个算法的时间复杂度是O(N* log2 K),其情况如下:
假设N个数存储在数组S中,我们从数组S中随机的找到一个元素X,把数组分成两部分Sa和Sb。Sa中的元素大于等于X,Sb中的元素小于X。
(1)Sa中的元素个数小于K,Sa中所有的数和Sb中最大的K-|Sa|个元素就是数组S中最大的K个数。
(2)Sa中的元素的个数大于等于K,则需要返回Sa中最大的K个元素。
代码实现:

#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
//返回基准索引
int partition(int a[],int low,int height)

   //存储基准值对应数组的索引
   int index;
   //利用随机选择比较基准值,以避免特殊数据下的算法退化
    swap(a[low],a[low+rand()%(height-low+1)]);
    int p=a[low];//比较的基准值
    int i=low,j=height;
    do
    
       //从右端开始寻找大于基准值的第一个值
      while(i<j&&a[j]<p)j--;
      //比基准值大的数往左边移
      if(i<j)
         a[i++]=a[j];
      //从左端端开始寻找小于基准值的第一个值
      while(i<j&&a[i]>=p)i++;
       //比基准值小的数往右边移
      if(i<j)
         a[j--]=a[i];
    while(i<j);
    a[i]=p;
    index=i;
    return index;

int Kbig(int a[],int low,int height,int k)

   if(low>=height)
      return a[low];
   int index=partition(a,low,height);
   //获得数组连续最大的数目
   int count=index-low+1;
   //获得数组连续最大的数目等于K则输出
   // 若小于K则是第一种情况
   //若大于K则是第二种情况
   if(count==k)
      return a[index];
   if(count<k)
      return Kbig(a,index+1,height,k-count);//后面部分寻找最大的K个数
   else
      return Kbig(a,low,index-1,k);//前面部分寻找最大的K个数

//交换数据
void swap(int *a,int *b)

   int temp=*a;
   *a=*b;
   *b=temp;

int main()

   int a[]=100,2000,45,68,5,700,9,50,45,89,87;
   int len=sizeof(a)/sizeof(int);
   int k=5;
   cout<<Kbig(a,0,len-1,k)<<endl;
   for(int i=0;i<k;i++)
      cout<<a[i]<<"\\t";
    return 0;


解法三思路:
寻找N个数中最大的K个数,本质上就是寻找最大的K个数中最小的那个,也就是第K大的数,因此可以使用二分搜索的策略,对一个给定的数p,可以在O(N)的时间复杂度内找出所有不小于P的数,假如N个常数最大的数为Vmax,最小的数为Vmin,那么这N个数中的第K大数一定在区间[Vmin,Vmax]之间,整个算法的时间复杂度也为O(N* log2 N).
代码实现:

#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
int find(int*a,int x,int len)

   int number=0;
   for(int i=0;i<len;i++)
      if(a[i]>=x)
         number++;
   return number;

int findKMax(int *a,int max,int min,int k,int len,int delta)

   while(max-min>delta)
         
      int mid=min+(max-min)/2;
       //若后面部分大于mid的个数大于K,那么查找后半部分
      if(find(a,mid,len)>=k)
         min=mid;
      //若后面部分大于mid的个数小于K,那么查找前半部分
      else
         max=mid;
   
   return min;

int main()

   int a[]=100,2000,45,68,5,700,9,50,45,89,87;
   int len=sizeof(a)/sizeof(int);
   int k=5;
   //该取值要比所有N个数中的任意两个不相等的元素差值之最小值都要小
   int delta=1;
   cout<<findKMax(a,2000,5,5,len,delta)<<endl;   
    return 0;

解法四思路
如果数据很大,100亿多个,这个时候数据不能全部装入内存,所以要求尽可能少地遍历所有数据,如果能通过逐渐的加入元素来判断,能就可以解决这问题了,因此可以用最小堆的结构来求出前面已加入元素的前K个数,因此可以构造K个元素的小顶堆来存储最大的K个数,最小堆的堆顶(Y)就是最大的K个数中的最小的一个,对于每一个新加入的数X,如果X < Y,则不需要更新堆,如果X>Y,那么用X替换掉堆顶的元素Y,然后调整小顶堆结构,整个过程遍历一次数组元素就行了,因此整个算法的时间复杂度O(N * log2 K ).
代码实现:

#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
//调整堆
void adjustMinHeap(int *a,int m,int n)

   int i=m;
   int j=2*i+1;//左结点
   int temp;//临时存储
   while(j<n)
   
      //比较左右结点得到较小的那个结点
      if(j+1<n&&a[j]>a[j+1])
         j++;
      //若父结点大于较小的那个子结点,则交换,否则就跳出循环
      if(a[i]>a[j])
      
         temp=a[j];
         a[j]=a[i];
         a[i]=temp;
         i=j;
         j=2*i+1;
      
      else
         break;
   

//创建k个元素最小堆
void createMinHead(int *a,int k)

   //自下而上的建立堆,从最后一个非叶子结点开始初始化最小堆
   for(int i=k/2-1;i>=0;i--)
      adjustMinHeap(a,i,k);

void FindKMax(int *a,int n,int *kMax,int k)

   for(int i=0;i<k;i++)
      kMax[i]=a[i];
   //初始K个元素最小堆
   createMinHead(kMax,k);
   for(int i=k;i<n;i++)
   
      //如果该元素大于她的最小堆则替换
      if(a[i]>kMax[0])
      
         kMax[0]=a[i];
         adjustMinHeap(kMax,0,k);
      
   

int main()

   int a[]=100,2000,45,68,5,700,9,50,45,89,87;
   int len=sizeof(a)/sizeof(int);
   int k=5;
   int * kMax=(int*)malloc(k*sizeof(int));
   FindKMax(a,len,kMax,k);
   for(int i=0;i<k;i++)
      cout<<kMax[i]<<"\\t";
    return 0;

第五中解法思路:
利用计数排序思想,如果所有N个数都是正整数,且它们的取值范围不太大,可以考虑申请空间,记录每个整数出现的次数,然后再从大到小取最大的K个,比如,所有整数都在(0,MAXN)区间中的话,利用一个数组count[MAXN]来记录每个整数出现的个数(count[i]表示整数i在所有整数中出现的个数),我们只须扫描一遍就可以得到count数组,然后寻找第K大的元素,整个算法时间复杂度为O(n).
代码实现:

#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
#define MAX 100000
int FindKMax(int *a,int k)

   int sumCount=0;
   int i;
   //从大到小开始计算其元素个数,直到为K
   for(i=MAX-1;i>=0;i--)
   
      sumCount+=a[i];
      if(sumCount>=k)
         break;
   
   return i;

int main()

   int a[]=100,2000,45,68,5,700,9,50,45,89,87;
   int len=sizeof(a)/sizeof(int);
   int count[MAX]=0;
   int k=5;
   //统计a元素对应出现个数
   for(int i=0;i<len;i++)
   
      count[a[i]]++;
   
   cout<<FindKMax(count,k)<<endl;
    return 0;

以上是关于寻找最大的K个数的主要内容,如果未能解决你的问题,请参考以下文章

寻找最大的K个数

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

寻找最小的k个数

寻找最大或最小的K个数

单调栈&单调队列入门

最小的k个数