TopK问题

TopK问题

今天看完了排序,想到了之前写过的一道题,输出前K大元素。
有两种方法,这里只要介绍一种基于快排的方法。

快排

由于我们只需要取前K大,所以不需要多余的元素,我们容易利用快排的性质,选取 主元 p i v o t pivot pivot之后,如果

1)比 p i v o t pivot pivot大的元素数目之和等于 k k k,那么停止算法,位于 p i v o t pivot pivot右边的元素就是我们所要的。
2)比 p i v o t pivot pivot大的元素数目之和大于 k k k,那么继续递归去找 p i v o t pivot pivot左边的前 k − m k-m km个元素, m m m是包括 p i v o t pivot pivot自身和其右边所有元素的数目。
3)比 p i v o t pivot pivot大的元素数目之和小于 k k k,那么继续递归去找 p i v o t pivot pivot右边元素的前 K K K个元素。

原问题形式为: a r r a n g e R i g h t ( l e f t ,    r i g h t ,    k ) arrangeRight(left, \ \ right,\ \ k) arrangeRight(left,  right,  k)
下面对应三个子问题:( i i i p i v o t pivot pivot的位置)
1) 结束算法。
2) a r r a n g e R i g h t ( l e f t ,    i ,    k − m ) arrangeRight( left, \ \ i,\ \ k-m) arrangeRight(left,  i,  km)
3) a r r a n g e R i g h t ( i ,   r i g h t ,   k ) arrangeRight(i,\ right,\ k) arrangeRight(i, right, k)

这里借用一下郭炜老师的PPT哈哈(我爱郭老师)
在这里插入图片描述

平均复杂度分析
m m m是常数
T ( N ) = T ( N / 2 ) + m ∗ N \quad T(N) = T(N/2) + m*N T(N)=T(N/2)+mN
    = T ( N / 4 ) + m ∗ N + m ∗ N / 2 \qquad \quad \ \ \ = T(N/4) +m*N+m*N/2    =T(N/4)+mN+mN/2
    = T ( N / 8 ) + m ∗ N + m ∗ N / 2 + m ∗ N / 4 \qquad \quad \ \ \ = T(N/8) +m*N+m*N/2+m*N/4    =T(N/8)+mN+mN/2+mN/4
    = . . . . . . \qquad \quad \ \ \ = ......    =......
    = T ( 1 ) + m ∗ N + m ∗ N / 2 + m ∗ N / 4 + . . . \qquad \quad \ \ \ = T(1) + m*N+m*N/2+m*N/4+...    =T(1)+mN+mN/2+mN/4+...
    < 2 ∗ m ∗ N \qquad \quad \ \ \ <2*m*N    <2mN

所以算法的平均时间复杂度是 O ( N ) O(N) O(N)

代码

题目链接(openjudge)
之前写的代码了,感觉有些地方写的不太好。

#include<iostream>
#include<algorithm>
#define maxsize 100010
using namespace std;
int a[maxsize], n, k;

void arrangeRight(int a[],int left,int right,int k)
{
    if(left>=right)	return;
    int i=left,j=right,temp=a[i];
    while(i!=j)
    {
        while(i<j&&a[j]>=temp) j--;
        swap(a[i],a[j]);
        while(i<j&&a[i]<temp)  i++;
        swap(a[i],a[j]);
    }
    if (right-i+1==k)  return;
    else if (right-i+1>k)
        arrangeRight(a,i+1,right,k);
    else if (right-i+1<k)
        arrangeRight(a,left,i-1,k-(right-i+1));
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    cin>>k;
    arrangeRight(a, 0, n-1, k);
    sort(a+n-k, a+n);
    for(int i = n-1; i >= n-k; i--)
        cout<< a[i] << endl;   
    return 0;
}

堆排序

思想很简单,就是维护一个只有 k k k个元素的最小堆,扫描一遍所有元素。每次遇到一个元素如果比堆顶元素大用这个元素替换掉堆顶元素。
最后留在堆里的就是答案。

复杂度分析
遍历一遍所有元素,复杂度 O ( N ) O(N) O(N)
那么在遍历的时候每次对堆的操作的复杂度呢?首先我们可以确定,对堆的操作复杂度是 l o g ( K ) log(K) log(K) K K K是堆中元素的数量,那么这就是一个平均的操作复杂度。在 K K K相对于 N N N很小的情况下,这个就可以忽略不计,也就是说复杂度还是 O ( N ) O(N) O(N),不过堆的话常数可能会大一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值