为什么基于快排的查找第k大数时间复杂度是O(n)

我们都知道,查找第k大数有一个很常用的方法,是基于快排的查找,思路跟快排基本一样,代码如下:

    public int findKthLargest(int[] nums, int k) {
        return (findKthNum(nums,0,nums.length-1,k-1));
    }

    private int findKthNum(int[] nums, int left, int right, int k){
        int stan = nums[right];
        int i = left, j = right;
        if (left == right && left == k) {
            return nums[left];
        }
        else {
            while (i < j) {
                while (i < j && nums[i] >= stan) {
                    i++;
                }
                while (i < j && nums[j] <= stan){
                    j--;
                }
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
        nums[right] = nums[i];
        nums[i] = stan;
        if (k < i) {
            return findKthNum(nums, left, i - 1, k);
        }
        else if (k > i) {
            return findKthNum(nums, i + 1, right, k);
        }
        else {
            return nums[i];
        }
    }

因为每次分割完只需要继续操作一边,所以时间复杂度是O(n)。

但光是这样解释让人有点不清楚,不通过计算很难让人明白为什么是O(n),比如,即便每次只操作一边,那么遍历的次数也是logn次,每次遍历是O(n),很容易让人误解为该方法是O(nlogn)。

所以这里还是要回到时间复杂度专门的递推公式上来。

我们都知道,快速排序的理想以及平均时间复杂度是O(nlogn),其递推公式如下,跟归并排序基本无异,只是多了一个在理想或者平均的限制,因为快排的最坏是O(n^2)

平均情况下:T(n)=2*T(n/2)+n;      第一次划分

      =2*(2*T(n/4)+n/2)+n;     第二次划分  (=2^2*T(n/4)+2*n)

      =2*(2*(2*T(n/8)+n/4)+n/2)+n;     第三次划分(=2*3*T(n/8)+3*n)

      =.....................

      =2^m+m*n;  第m次划分

因为2^m=n,所以等价于 = n+m*n
所以m=logn,所以T(n)=n+n*logn;     

这边还是比较好理解的,那么基于快排的查找第k大数的时间复杂度如下:

平均情况下:T(n) = T(n/2) + n;      第一次划分

      = T(n/4) + n/2 + n;     第二次划分  

      = T(n/8) + n/4 +n/2 +n;     第三次划分

      =.....................

      = T(n/n) + 2 + 4 + ... + n;  第m次划分

是一个等比数列的求和公式,那么显然T(n) = 2n

所以计算时间复杂度最好严谨按照递推公式来,那么这里注意基于快排查找第k大数,也是平均时间复杂度为O(n)。

《算法导论》介绍到一个最坏情况下也是O(n)的方法,这里不做详细介绍,参考http://blog.chinaunix.net/uid-26456800-id-3407406.html

 

posted @ 2020-03-17 18:03  咕咕刘三刀  阅读(2050)  评论(0编辑  收藏  举报