算法第二章实践作业

  1. 找第 k 小数的分治算法描述

该算法通常基于快速排序中的 Partition 划分思想,也叫“快速选择”或 QuickSelect。设数组为 A[p..r],要找其中第 k 小的数,k 从 1 开始计数。

自然语言描述:

  1. 若当前区间 A[p..r] 只有一个元素,则该元素就是答案。
  2. 从区间中选一个枢轴元素 pivot。
  3. 对区间进行划分,使得:
    • 小于等于 pivot 的元素放在左边;
    • 大于 pivot 的元素放在右边;
    • 同时确定 pivot 最终所在位置 q。
  4. 计算 pivot 在当前区间中的排名:
    leftLen = q - p + 1
  5. 比较 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

实际实现中,通常随机选择一个元素作为枢轴,以尽量避免最坏情况。

  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)

不过其常数较大,实际工程中不一定比随机快速选择更快。

  1. 对分治法的体会和思考

找第 k 小数的算法很好地体现了分治法的核心思想:分解、求解、合并。它先通过划分把原问题分解成两个子区间,然后根据第 k 小数的位置,只递归进入其中一个子区间,而不是两个都处理。严格来说,它更接近“减治”,因为每次只解决一个子问题。

从中可以得到几点体会:

  1. 划分质量决定效率
    快速选择的关键在于枢轴选择。如果枢轴把数组划分得比较均衡,问题规模快速下降,时间复杂度接近 O(n);如果划分极不均衡,就会退化为 O(n^2)。这说明分治算法中,如何划分子问题非常重要。

  2. 不需要解决所有子问题
    快速排序需要递归处理左右两部分,因此平均复杂度是 O(n log n);而找第 k 小数只需要进入包含答案的一侧,因此平均复杂度可以降到 O(n)。这说明分治并不一定要处理所有子问题,合理“剪枝”可以显著提高效率。

  3. 随机化与最坏情况保证的权衡
    随机选择枢轴实现简单、常数小、期望性能好,但最坏情况仍是 O(n^2)。中位数的中位数算法可以保证最坏 O(n),但实现复杂、常数大。实际应用中常根据需求选择:追求简单高效可用随机快速选择,追求理论最坏保证可用 BFPRT。

  4. 分治法不仅是递归,更是问题规模的缩减
    分治法有效的关键在于子问题规模要显著小于原问题。如果每次只减少一个元素,就会退化。因此设计分治算法时,要关注递归式,例如:

T(n) = T(n/2) + O(n)

与

T(n) = T(n-1) + O(n)

的差别是巨大的。

总之,找第 k 小数的分治算法让我更深刻地理解了:分治法的核心不只是“分”,更在于“如何分”和“分完之后如何舍弃无关部分”。好的划分策略和合理的递归结构,是分治算法高效的关键。

posted @ 2026-10-13 22:45  最好的大虾食用者  阅读(3)  评论(0)    收藏  举报