编程之美读书笔记2.5—寻找最大的K个数

给定一个N个数的数组arr[N],任意排列,选出其中最大的K个数。

方法1:排序,取后K个数。复杂度: O(NlogN)
方法2:partition,随意选一个数组中的数key,对数组进行划分,返回位置pos,如果pos右侧的数等于K,则结束,如果大于K,则再对从pos到数组尾的子数组进行partition,如果小于K,则对从数组头到pos位置的子数组进行partition,找出剩下的元素。时间复杂度:O(NlogK)
void printarray(int *arr,int beg,int end)
{
    for(int i = end-1; i>=beg-1; --i)
    {
        cout<<arr[i]<<' ';
    }
}

int partition(int *arr,int beg,int end)
{
    if(beg<1||beg>end)
    {
        cout<<"error input"<<'\n';
        exit(0);
    }
    if(beg==end)
         return beg;
    int i = beg-2;
    int j = beg-1;
    int key = arr[end-1];
    while(j<end-1)
    {
        if(arr[j]<=key)
        {
            ++i;
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        ++j;
     }
    int temp = arr[end-1];
    arr[end-1] = arr[++i];
    arr[i] = temp;
    return i+1;
}

void maxK(int *arr,int beg,int end,int K)
{
    if(beg<1||beg>end)
    {
        cout<<"error input"<<'\n';
        exit(0);
    }
    if(end-beg+1==K)
    {
        printarray(arr,beg,end);
        return;
    }
    int mid = partition(arr,beg,end);
    int tmp = end-mid+1;
    if(tmp==K)
          printarray(arr,mid,end);
    else if(tmp<K)
    {
        printarray(arr,mid,end);
        maxK(arr,beg,mid-1,K-tmp);
    }
    else
        maxK(arr,mid+1,end,K);
}
int main()
{
int a[]={2,5,8,7,4,1,3,6};
maxK(a,1,8,4);


return 0;
}




3.可以使用二分搜索的策略来寻找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的取值受到内存的限制。

 


方法4:最小堆,建立一个K元素的最小堆,用arr的前K个元素初始化,然后从arr的第K+1个元素开始扫描数组,如果大于最小堆的根,则互换元素,并对根做minheap_fy,这样最后最小堆里剩的就是最大的K个元素.时间复杂度:O(NlogK)
void minheap_fy(int *heap,int index,int heapsize)
{
      int left = index<<1;
     int right = left + 1;
     int smallest = index;
     if(left<=heapsize&&heap[smallest-1]>heap[left-1])
           smallest = left;
     if(right<=heapsize&&heap[smallest-1]>heap[right-1])
           smallest = right;
     if(smallest != index)
     {
           int temp = heap[index-1];
           heap[index - 1] = heap[smallest-1];
           heap[smallest-1] = temp;
           minheap_fy(heap,smallest,heapsize);
     }
}

void build_minheap(int *heap,int heapsize)
{
     int temp = heapsize/2;
     while(temp>0)
     {
           minheap_fy(heap,temp,heapsize);
           --temp;
     }
}

void maxK2(int *arr,int size,int K)
{
     if(size<K)
     {
           cout<<"error K"<<'\n';
           return;
     }
     int *heap = new int[K];
     for(int i = 0;i<K;++i)
     {
           heap[i] = arr[i];
     }
     build_minheap(heap,K);
     for(int i = K; i<size; ++i)
     {
           if(arr[i]>heap[0])
           {
                 heap[0] = arr[i];
                 minheap_fy(heap,1,K);
           }
     }
     for(int i = 0;i<K;++i)
           cout<<heap[i]<<' ';
     delete[] heap;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值