week2

一 分治法怎么做
问题:给定 n 个互不相同(或有重复)的数 a 和一个整数 k (1≤k≤n ),找出其中第 k 小的数(即排序后第 k 个位置上的数)。特别地, k=1 是最小值, k=n 是最大值, k=⌈n/2⌉ 是中位数。
最笨但正确的做法:先排序,再取下标 k。代价 Θ(nlogn) 。
关键洞察:找第 k 小不需要把整个序列排好序,只需要知道“谁站在第 k 个位置上”。排序做了大量“多余的工作”——它把不该排的部分也排了。分治的精髓就在于:每次只进入有必要的那一半,扔掉另一半。
这个洞察直接引出了下面的算法。它的思想源头是快速排序的 partition(划分):选一个枢轴 pivot,把比它小的放左边、比它大的放右边。划分完后,pivot 落在了它的“最终位置” p 上:
若 p=k :找到了,pivot 就是答案;
若 p>k :答案一定在左半段,右边整段丢弃;
若 p<k :答案一定在右半段,左边整段丢弃,且只要在右半段里找第 k−p 小即可。
二 时间复杂度
无论哪种版本,partition 这一步都要扫描整个当前区间,代价是 Θ(区间长度) 。所以任何情况下总工作量都不可能低于 Θ(n) ——这是问题的下界(至少要把每个数看一遍才知道谁第 k 小)。因此最好时间复杂度的上界就是 Θ(n) ,不可能出现 O(1) 、 O(logn) 这种答案。
随机化选择 Randomized-Select
最好情况:第一次选的 pivot 恰好就是第 k 小的元素。此时只做一次 partition 就结束:T best(n)=Θ(n)
最坏情况:每次都选到极值(例如数组已有序、且总是选到最小或最大元素),每次只丢掉 1 个元素,递归深度为 n:T worst(n)=T(n−1)+Θ(n)=Θ(n^2 )
这与快排的最坏情况同源。例如在升序数组中找最大值,且每次都取首元素作 pivot,就会逐次只排除一个元素。
三 对分治法的体会与思考
分而治之,我们无法一次性处理规模为 n 的问题,但如果能把问题切成足够小的、结构相同的碎片,碎片就会自己给出答案。而算法设计,就体现在“怎么切”——切得太浅,省不下工夫;切得太狠,切分本身的代价就吃掉了收益。选择问题之所以重要,正是因为它展示了同样的切分思想,换一个 pivot 策略,就能从 O(n^2 ) 走到 O(n) 。这也说明同一个分治框架下,细节决定量级。
运用主定理。看到一段递归就能立刻写出 T(n)=aT(n/b)+f(n) ,然后凭 log b a 与 f(n) 的对比判断它是 O(n) 、 O(nlogn) 还是 O(n^2) 。遇到主定理套不上的(如本题的 T(n/5)+T(7n/10)+n ,两个子问题规模不同),就回到递归树或代入法,当工具失效时就回归定义。
随机化选择的好在于:最坏情况不再由输入决定,而是由随机数生成器决定。因此对任意固定输入,算法出错的概率都可控。
计算机的实际运行:先用快选,一旦递归深度超过某个阈值就切换到 BFPRT。这就是“理论保底 + 实践优先”的混合策略。
分治三步曲——分解(Divide)、解决(Conquer)、合并(Merge),真正决定成败的是两条:子问题必须独立且同构(否则无法递归);合并的代价必须压得住(否则省不下来)。归并排序之所以成功,是因为合并两个有序表只要 O(n) ;如果合并要 O(n^2) ,整个算法就废了。本题的选择问题更进一步:它甚至不需要合并,因为答案只在一边,另一边的信息彻底无用。这说明分治不是死板的模板,而是一种“能不能通过舍弃来省钱”的思维方式.
分治不是万能药,它怕两件事:子问题不独立、子问题太多。
比如最长公共子序列如果暴力分成四种情况,会产生重叠子问题,纯分治会指数爆炸,这时必须升级为动态规划(分治 + 记忆化)。反过来,如果划分后子问题个数太多(如 Strassen 之前的大整数乘法是 4 个子问题,仍是 O(n 2) ),就必须靠数学技巧减少子问题数目(Strassen 降到 7 个,才变成 O(n 2.81 ) )。所以分治、贪心、动规其实是一条谱系上的不同落点,区别在于最优子结构的形态和有没有重叠子问题。

posted @ 2026-10-11 19:35  hxt^^  阅读(4)  评论(0)    收藏  举报