为什么基于快排的查找第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