算法第二章实践作业
- 找第 k 小数的分治算法描述
该算法通常基于快速排序中的 Partition 划分思想,也叫“快速选择”或 QuickSelect。设数组为 A[p..r],要找其中第 k 小的数,k 从 1 开始计数。
自然语言描述:
- 若当前区间 A[p..r] 只有一个元素,则该元素就是答案。
- 从区间中选一个枢轴元素 pivot。
- 对区间进行划分,使得:
- 小于等于 pivot 的元素放在左边;
- 大于 pivot 的元素放在右边;
- 同时确定 pivot 最终所在位置 q。
- 计算 pivot 在当前区间中的排名:
leftLen = q - p + 1 - 比较 k 与 leftLen:
- 若 k == leftLen,则 A[q] 就是第 k 小数;
- 若 k < leftLen,说明第 k 小数在左半部分,递归在 A[p..q-1] 中找第 k 小;
- 若 k > leftLen,说明第 k 小数在右半部分,递归在 A[q+1..r] 中找第 k-leftLen 小。
伪代码:
Select(A, p, r, k)
if p == r
return A[p]
q = Partition(A, p, r) // 返回枢轴最终位置
leftLen = q - p + 1
if k == leftLen
return A[q]
else if k < leftLen
return Select(A, p, q - 1, k)
else
return Select(A, q + 1, r, k - leftLen)
Partition(A, p, r)
x = A[r] // 可选:随机选枢轴后交换到 A[r]
i = p - 1
for j = p to r - 1
if A[j] <= x
i = i + 1
swap(A[i], A[j])
swap(A[i + 1], A[r])
return i + 1
实际实现中,通常随机选择一个元素作为枢轴,以尽量避免最坏情况。
- 时间复杂度分析
设当前问题规模为 n。
一次划分需要扫描当前区间,时间复杂度为:
O(n)
最好时间复杂度:
最好情况下,每次划分后第 k 小数所在的一侧规模迅速减小,例如每次大约缩小为原来的一半:
T(n) = T(n/2) + O(n)
展开得:
T(n) = O(n) + O(n/2) + O(n/4) + ... = O(n)
更极端地,如果第一次划分后枢轴正好就是第 k 小数,则只需一次划分,时间也是 O(n)。
因此最好时间复杂度为:
Θ(n)
最坏时间复杂度:
最坏情况下,每次选择的枢轴都是当前区间的最小值或最大值,并且第 k 小数位于较大的那一侧。例如数组已经有序,而每次都选最后一个元素作为枢轴,则每次只能排除一个元素:
T(n) = T(n-1) + O(n)
展开得:
T(n) = O(n) + O(n-1) + ... + O(1) = O(n^2)
所以最坏时间复杂度为:
O(n^2)
如果使用随机化选择枢轴,则期望时间复杂度为:
O(n)
最坏仍然是 O(n^2),但出现概率很低。
如果使用“中位数的中位数”算法选择枢轴,即 BFPRT 算法,则可以保证最坏情况下:
T(n) = O(n)
不过其常数较大,实际工程中不一定比随机快速选择更快。
- 对分治法的体会和思考
找第 k 小数的算法很好地体现了分治法的核心思想:分解、求解、合并。它先通过划分把原问题分解成两个子区间,然后根据第 k 小数的位置,只递归进入其中一个子区间,而不是两个都处理。严格来说,它更接近“减治”,因为每次只解决一个子问题。
从中可以得到几点体会:
-
划分质量决定效率
快速选择的关键在于枢轴选择。如果枢轴把数组划分得比较均衡,问题规模快速下降,时间复杂度接近 O(n);如果划分极不均衡,就会退化为 O(n^2)。这说明分治算法中,如何划分子问题非常重要。 -
不需要解决所有子问题
快速排序需要递归处理左右两部分,因此平均复杂度是 O(n log n);而找第 k 小数只需要进入包含答案的一侧,因此平均复杂度可以降到 O(n)。这说明分治并不一定要处理所有子问题,合理“剪枝”可以显著提高效率。 -
随机化与最坏情况保证的权衡
随机选择枢轴实现简单、常数小、期望性能好,但最坏情况仍是 O(n^2)。中位数的中位数算法可以保证最坏 O(n),但实现复杂、常数大。实际应用中常根据需求选择:追求简单高效可用随机快速选择,追求理论最坏保证可用 BFPRT。 -
分治法不仅是递归,更是问题规模的缩减
分治法有效的关键在于子问题规模要显著小于原问题。如果每次只减少一个元素,就会退化。因此设计分治算法时,要关注递归式,例如:
T(n) = T(n/2) + O(n)
与
T(n) = T(n-1) + O(n)
的差别是巨大的。
总之,找第 k 小数的分治算法让我更深刻地理解了:分治法的核心不只是“分”,更在于“如何分”和“分完之后如何舍弃无关部分”。好的划分策略和合理的递归结构,是分治算法高效的关键。

浙公网安备 33010602011771号