码迷,mamicode.com
首页 > 其他好文 > 详细

寻找最大的K个数

时间:2015-09-02 02:06:37      阅读:277      评论:0      收藏:0      [点我收藏+]

标签:堆排序   计数排序   快速排序   二分查找   寻找数组k个元素   

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

#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个元素   

原文地址:http://blog.csdn.net/xuguoli_beyondboy/article/details/48162405

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!