算法第二章
找第k小的数
找第k小的数可以用分治法来做。思路是:在当前区间里选一个基准数,比如选第一个数。然后做一次划分,让左边都不大于基准,右边都不小于基准。划分后基准会落在位置p。这时候基准在当前区间里的排名是 rank = p - left + 1。如果 k 等于 rank,说明基准就是第k小,直接返回。如果 k 小于 rank,说明第k小在左边,就去左边继续找。如果 k 大于 rank,说明第k小在右边,就去右边继续找,但这时候要找的是右边的第 k - rank 小。每次只递归一边,所以平均比较快。
伪代码可以写成:
find(a, left, right, k)
如果 left 等于 right,返回 a[left]
p = partition(a, left, right)
rank = p - left + 1
如果 k 等于 rank,返回 a[p]
如果 k 小于 rank,返回 find(a, left, p - 1, k)
否则,返回 find(a, p + 1, right, k - rank)
时间复杂度分析:
最好情况是每次划分都很均匀,基准差不多在中间。这样每次只处理一半,时间大约是 n + n/2 + n/4 + ...,所以最好时间复杂度是 O(n)。
最坏情况是每次划分都很偏,比如基准总是最大或最小。这样每次只减少一个元素,时间大约是 n + (n-1) + (n-2) + ...,所以最坏时间复杂度是 O(n^2)。
平均情况下,划分比较均匀,所以平均时间复杂度是 O(n)。
分治法学习体会:
分治法不一定非要左右两边都递归。找第k小就是只递归可能有答案的那一边,另一边直接不管,这样效率更高。它让我明白,分治的关键是把问题分开,然后只处理真正需要的部分。划分是否均匀很重要,如果每次都很偏,效率会变差,所以可以用随机选基准来避免最坏情况。另外,递归右边时 k 要减去 rank,这个细节也说明分治里参数调整很重要。总的来说,分治法是一种把大问题拆小、减少工作量的思想。

浙公网安备 33010602011771号