计算机与信息 · 8/14
分而治之Divide and Conquer
把大问题拆成同类小问题,逐个求解,再按规则合回完整答案。
2019年温网男单冠军,是先决出各个小组的胜者,再让胜者相遇产生的。7月14日,在伦敦全英草地网球俱乐部的中央球场,德约科维奇击败费德勒,捧起奖杯。回看签表,128名选手分处上下两个半区,两人各自从一个半区晋级。半区继续分成更小的组,直到最小的一组只剩两人,打一场比赛就有答案。每组胜者向上一轮汇合,决赛再把两个半区的结果合成一个冠军。
为什么成立
每次拆分都保留同一种问题
给8个乱序数字排序,可以先各排好左右4个,再合成一列。每半又拆成两份,直到每份只剩一个数字,天然有序。每次要做的都是排序,只是规模更小。分而治之就沿着这个结构,先向下拆,再从最小的答案向上合。
合并规则保证小答案能拼成大答案
合并两列已经排好的数字时,只要比较各列最前面的数字,把较小的取走,反复做下去。因为每列后面的数字都不小于它的首项,这次取出的就是剩余数字中最小的。拆分能否成立,关键在于小答案是否保留了合成完整答案所需的信息。两列各自排好还不够,合并这一步才保证整体有序。
节省多少工作取决于拆分与合并的代价
把归并排序的工作量记作,当是2的整数次幂时:
白话说,总工作量等于排好两个半份的工作,再加上随数字数量成比例的拆分与合并工作。表示这一部分按线性规模增长。每层合并共处理个数字,合并共有层,因此总工作量是。收益来自这种安排减少了比较次数;一个人依次做完所有步骤,也能得到这个收益。
出处
分而治之在计算机科学中被系统化为算法设计方法,没有唯一的发明者。冯·诺依曼在1945年提出的归并排序是早期经典;库利与图基在1965年发表的快速傅里叶变换算法,则展示了把大计算拆成同类小计算能带来多大的效率提升。
换个领域看
技术
把长计算拆短
1965年,IBM的库利与普林斯顿大学、贝尔实验室的图基共同发表快速傅里叶变换算法。在处理长度为2的整数次幂的数据时,可以把奇数位置与偶数位置的数据分开,各做一次较小的变换,再按数学规则合成完整结果。对子序列继续这样拆,计算量便从平方级降到级,算出的仍是同一个离散傅里叶变换。
投资
逐层估值再合并
假设你研究一家经营食品和日用品的集团,可以先拆成两块业务,再各自按产品线继续拆小。每条产品线用适合其风险的折现率,把未来现金流换算成今天的价值,再逐层相加。总部费用的现值在集团层扣一次,最后减去净债务,得到股权价值。合并规则写清共享费用归谁扣,小估值才能拼成完整估值。
个人生活
分箱清点藏书
假设你搬家后要核对八箱书是否全部到齐,可以先分成两组,每组再分成两箱,最后逐箱核对书单。每箱留下已到数量和缺书名单,向上合并时,数量相加,名单汇总。你每次只盯一箱,最后仍能回答全部书是否到齐。打包时,每本书只登记在一张箱单上,核对时就不会重复计入。
遇事时问自己
- 这个大问题能否拆成几个规模更小、仍用同一种办法求解的问题?
- 拆到多小时,我能直接给出答案?
- 每个小答案必须留下哪些信息,才能合成完整答案?
- 跨部分的关系、共享成本和重复项目,在哪一步处理?
- 加上拆分、协调与合并的工作后,总工作量真的减少了吗?
边界与误用
当各部分的选择会互相改变结果,而且合并时无法补回这些影响,拆开求解就会丢掉整体答案。例如,投资组合里每只资产单独看都不错,合在一起却会共同暴露于同一种风险;风险不能按单项简单相加。最常见的误用是把任务分给不同部门,就认定已经解决了问题,却没写清结果怎样接起来。拆得过细、各份规模严重失衡,或合并比原任务还费力时,效率收益也会消失。
练一练
运维人员要从一周的记录中找出最长连续断线分钟数。记录按时间排列,每分钟只有“在线”或“断线”两种状态。他把记录不断对半切分,直到每份只剩一分钟,再逐层合并摘要。连续断线可能跨过切分点。
每份摘要保留哪组信息,才能在合并时准确算出答案?
合并时,比较两段内部的最长断线,以及左段结尾加右段开头的断线长度。总分钟数还能帮助判断某段是否全部断线,从而更新新摘要的首尾长度。
断线总数和首尾状态看起来能概括这一段。可首尾状态没有说明边缘连续断了多久,无法算出跨切分点的断线长度。
首次和末次时间看起来能定位断线边界。可两者之间可能恢复过在线,它们不能说明开头和结尾各连续断线多久。
这些统计能描述断线有多频繁、多严重。可它们没有保留断线在切分点附近的位置,跨段的连续断线仍可能漏算。
每一层都在求同一种答案:这一段最长连续断线多久。小答案除了段内最大值,还要留下边缘信息,才能把跨切分点的情况补进完整答案。
研究员要从4096条债券报价中找出最高到期收益率,只比较已经算好的收益率数值,各条数值不同。原办法是从头扫描,共比较4095次。新办法不断均分报价,各组留下最高值,再逐层比较组内赢家。全部步骤仍由他一个人依次完成。
关于新办法的比较次数,哪种判断最站得住?
要从4096个候选中留下一个,每次比较排除一个,共需4095次。拆分改变了比较的组织方式,没有减少这里的比较次数。
12层确实比4095次听起来少很多。可每层有多个比较,层数表示合并的深度,不能当作全部比较的次数。
小组变小后,每组确实更容易处理。可组数也增加了,还要比较各组赢家,单组工作减少不代表总工作减少。
同一个赢家可能出现多次,看起来像重复劳动。可直接扫描时,当前最高值也会反复参与比较,两种办法都只需排除4095个候选。
这套拆分和合并规则能正确找出最高值,但正确拆开不等于节省工作。判断效率要算全部小问题和合并的代价,不能只看递归有多少层。
一座城市让南北两区分别优化交通信号,目标都是减少车辆等待时间。车辆会跨区行驶,两区还共用两座桥。各区只向市里提交自己的最佳配时表和最低等待时间。市里准备直接拼接两张表,宣布得到全城最优方案。
对这个结论,哪种判断最站得住?
加权能避免小区和大区被赋予同样的分量。可它只能汇总已有数字,不能补回配时改变车流后产生的新等待时间。
各区求解时面对的车流,会被另一侧的方案改变。要判断整体结果,必须处理跨区车流和桥梁容量的共同影响。
预留余量是应对车流变化的常见办法。可固定余量没有说明两张配时表如何共同改变车流,不能据此保住原来的最优结果。
目标相同让两区结果看起来可以直接相加。可一侧放行的车辆会改变另一侧的拥堵,拼接后各区原来的最低等待时间未必还能成立。
这个处境看起来也是把大问题拆小,但两区的选择会互相改变求解条件。小答案没有保留这些影响,直接拼接就无法保证得到整体最优答案。