使用快排解决TOP K的时间复杂度分析

一、TOP K问题

TOP K问题就是在一堆数据里面找到前 K 大的数。

二、算法

1.快排

设输入数组如下
在这里插入图片描述
我们把最大的K个数放在后面。

快排每次把选中的基准数放到它应该在的位置,然后它的左侧的数都比它小,右侧的数都比它大。

代码如下:

public static void Sort(int[] a, int l, int r) {
        if (l < r) {//如果l小于r了,说明已经完成这段排序将会直接返回
            int i,j,pivot;
            i = l;
            j = r;
            pivot = a[i];//选择值
            while (i < j) {
                while(i < j && a[j] > pivot)
                    j--; // 从右向左找第一个小于pivot的数
                if(i < j)
                    a[i++] = a[j];
                while(i < j && a[i] < pivot)
                    i++; // 从左向右找第一个大于pivot的数
                if(i < j)
                    a[j--] = a[i];
            }
            a[i] = pivot; //pivot已被排好序
            Sort(a, l, i-1); /* 递归半区排序 */
            Sort(a, i+1, r); /* 递归半区排序 */
        }
    }

在这里插入图片描述

2.TOPK版

TOP K问题中,我们只需要找出最大的几个数,但是不需要对它们进行排序。

在这里插入图片描述

比如,我们需要最大的4个数,令k=4。如上图所示,我们的基准数如果一开始恰好就选中了那个本应该排在第七个的数50,那么一次排序后,左侧的数都比50小,得到右侧4个比50大的数,TOP K问题就解决了。

比起快排,TOP K代码不需要两边都排序,因此在快排基础上只需要加个半区判断即可:

 private static void solution(int[] a, int l, int r,int k) {
        int i=l,j=r,pivot=a[i];
        while (i<j){
            while (i<j&&a[j]>pivot)
                j--;
            if (i<j)
                a[i++]=a[j];
            while (i<j&&a[i]<pivot)
                i++;
            if (i<j)
                a[j--]=a[i];
        }
        a[i]=pivot;
        if (i==a.length-1-k)
            return;
        else if (i<a.length-1-k)
            solution(a, i+1, a.length-1,k);
        else
            solution(a, 0, i-1,k);
    }

三、复杂度分析

首先看快排

最优的时候,每次pivot都恰好选在了中间,两个递归区间长度相同,此时时间复杂度为
T ( n ) = n + 2 ∗ T ( n 2 ) = n + 2 ∗ ( n 2 ) + 2 ∗ 2 ∗ T ( n 2 ∗ 2 ) + ⋯ + n + T ( 1 ) T ( n ) = n log ⁡ n \begin{gathered} T(n)=n+2 * T\left(\frac{n}{2}\right)=n+2 *\left(\frac{n}{2}\right)+2 * 2 * T\left(\frac{n}{2 * 2}\right)+\cdots+n+T(1) \\ T(n)=n \log n \end{gathered} T(n)=n+2T(2n)=n+2(2n)+22T(22n)++n+T(1)T(n)=nlogn
最糟糕的时候,每次pivot都选的是排序后最边上的值,这时候的复杂度为
T ( n ) = n 2 \begin{gathered} T(n)=n ^2 \end{gathered} T(n)=n2
平均情况下,每种可能性我们都考虑进去,出现的概率为 1 n \frac{1}{n} n1,则复杂度计算公式为
T ( n ) = n + 1 n ∑ i = 1 n ( T ( i − 1 ) + T ( n − i ) ) T(n)=n+\frac{1}{n} \sum_{i=1}^{n}(T(i-1)+T(n-i)) T(n)=n+n1i=1n(T(i1)+T(ni))
考虑到和式的对称性, 所以
T ( n ) = n + 2 n ∑ i = 0 n − 1 T ( i ) T(n)=n+\frac{2}{n} \sum_{i=0}^{n-1} T(i) T(n)=n+n2i=0n1T(i)
接下来解递推公式得到
T ( n ) = n log ⁡ n T(n)=n \log n T(n)=nlogn

接下来分析TOP K下的复杂度

最好的时候,我们每次选到中间,因为每次选完后只需要对其中一部分进行处理, 因此
T ( n ) = n + T ( n 2 ) = n + n 2 + 2 ∗ T ( n 2 ) + ⋯ T(n)=n+T\left(\frac{n}{2}\right)=n+\frac{n}{2}+2 * T\left(\frac{n}{2}\right)+\cdots T(n)=n+T(2n)=n+2n+2T(2n)+
由等比数列求和易知
T ( n ) = n T(n)=n T(n)=n
最糟糕的时候, 同样是每次我们都选到了最边上, 这时候每次选择后减少的处理次数也派不上用场,复杂度退化到
T ( n ) = n 2 T(n)=n^{2} T(n)=n2
平均情况下,对比快排复杂度计算的式子
T ( n ) = n + 1 n ∑ i = 0 n − 1 T ( i ) T(n)=n+\frac{1}{n} \sum_{i=0}^{n-1} T(i) T(n)=n+n1i=0n1T(i)
此时前面的 n \mathrm{n} n 受划分情况的影响,比如,第一次消耗为n,假设第一次刚好选择了中间的值,第二次处理其中一半即可,即 n 2 \frac{n}{2} 2n。假设第一次刚好选择了最边缘的值,第二次还是需要消耗n-1。

因此前面的 n \mathrm{n} n 受划分情况的影响
T ( n ) = 1 n ∑ i = 0 n − 1 ( i + T ( i ) ) T(n)=\frac{1}{n} \sum_{i=0}^{n-1}(i+T(i)) T(n)=n1i=0n1(i+T(i))

T ( n + 1 ) = 1 n + 1 ∑ i = 0 n ( i + T ( i ) ) T(n+1)=\frac{1}{n+1} \sum_{i=0}^{n}(i+T(i)) T(n+1)=n+11i=0n(i+T(i))
得到
( n + 1 ) T ( n + 1 ) − n T ( n ) = T ( n ) + n (n+1) T(n+1)-n T(n)=T(n)+n (n+1)T(n+1)nT(n)=T(n)+n
这里直接代入法求解就行了
T ( n ) = n T(n)=n T(n)=n

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值