找第k小的数——分治算法的一次实践与思考

找第k小的数——分治算法的一次实践与思考

一、问题背景

这周算法课讲分治法,老师布置了这道题:用分治的思想找出一组数中第k小的数。乍一看好像不难,排个序取第k个就行了,但那样是O(nlogn),而分治可以做到平均O(n)。这个差距还是挺吸引人的,所以我想认真梳理一下。

二、算法描述

找第k小的数,分治的思路其实和快速排序很像,核心就三步:分解、解决、合并。不过这里“合并”这一步基本不需要做什么,因为答案在分解的过程中就已经确定了。

用自然语言描述大概是这样的:

  1. 从数组里随便选一个数作为基准(pivot),一般选第一个或者随机选一个。
  2. 把数组重新排列,比pivot小的放左边,比pivot大的放右边。这时候pivot所在的位置就确定了,假设它的下标是p。
  3. 比较k和p的关系:
    · 如果k == p,那pivot就是第k小的数,直接返回。
    · 如果k < p,说明第k小的数在左半部分,递归在左边找第k小的数。
    · 如果k > p,说明在右半部分,递归在右边找第k-p小的数(注意这里k要减去p,因为左边已经有p个数比它小了)。

伪代码大概长这样:

function findKth(arr, left, right, k):
    if left == right:
        return arr[left]
    
    pivot = partition(arr, left, right)  // 返回基准最终位置
    pos = pivot - left + 1               // 基准是当前范围内的第pos小
    
    if k == pos:
        return arr[pivot]
    else if k < pos:
        return findKth(arr, left, pivot - 1, k)
    else:
        return findKth(arr, pivot + 1, right, k - pos)

partition函数就是快排里那个,用双指针从两端向中间扫,把小于基准的换到左边,大于的换到右边。

三、时间复杂度分析

最好情况

最好情况就是每次选基准都能把数组均匀分成两半。第一次partition要遍历n个数,第二次遍历n/2,第三次n/4……加起来就是:

n + n/2 + n/4 + ... ≈ 2n

所以最好时间复杂度是O(n)。

最坏情况

最坏情况是每次选的基准都是当前范围内的最大或最小数,比如数组已经有序,然后每次选第一个当基准。这样每次只能排除一个元素,递归深度变成n,每次partition的代价是n、n-1、n-2……加起来就是:

n + (n-1) + (n-2) + ... + 1 = n(n+1)/2

所以最坏时间复杂度是O(n²)。

平均情况

如果基准是随机选的,平均情况下每次能排除一部分元素,期望时间复杂度是O(n)。这个证明稍微复杂一点,但直观上理解就是每次不会太倒霉,递归深度大概是logn级别,总代价还是线性的。

四、对分治法的体会和思考

学完这一章,我对分治法有几个比较深的感受。

第一,分治法的关键在于“分解”得合理。 快排和找第k小用的都是partition,但找第k小时我们只需要递归一边,这就是分治灵活的地方——不是所有分治都要两边都处理。这让我意识到,分治不一定非要“分而治之”到底,有时候只治一边反而更高效。

第二,基准的选择很影响性能。 最坏情况O(n²)其实挺吓人的,所以实际中一般用随机选基准或者三数取中来避免。这让我想到,算法的理论复杂度和实际表现之间,往往需要一些工程上的小技巧来平衡。

第三,分治和递归是绑在一起的。 写代码的时候递归的边界条件特别重要,比如left == right的时候要直接返回,不然会死循环。还有就是k在右边递归时要减去pos,这个细节我一开始写错了,后来画了个图才理清楚。

第四,和排序的关系。 找第k小其实可以看成快排的“半成品”——快排两边都要排,而这里只需要一边。这种“只做一半”的思想在很多地方都有用,比如二分查找也是类似的思路。

总的来说,分治法不是一种具体的算法,更像是一种思考问题的方式:把大问题拆成小问题,然后利用小问题的解来构造大问题的解。但具体怎么拆、拆完怎么处理,还是要看问题的特点。找第k小这个例子就很好地体现了这一点——同样是分治,它比快排少做了一半的工作,效率反而更高。

写完发现,其实这道题最坑的地方就是k在右边递归时的偏移量,我第一遍写的时候直接传k进去,结果怎么都不对。后来在纸上模拟了一遍才反应过来,左边那pos个数已经确定比目标小了,所以右边只需要找第k-pos小的。这种细节,不自己动手写一遍真的很难发现。

posted @ 2026-10-13 20:18  与青山奏江河  阅读(4)  评论(0)    收藏  举报