想象你是一位名叫阿峰的登山爱好者,来到一片连绵起伏的山脉,目标是找到群山之中的最高峰。你带了一台精准的海拔仪,但制定了一条简单的规则:只看脚下,哪边地势高就往哪边走。很快,你爬上了一座小土丘——环顾四周,附近的土地确实都比这里低,你心满意足地宣布“这就是最高峰”。然而,远处云雾缭绕的地方,藏着一座巍峨得多的山峰,只是你永远看不到了。 这就是“局部最优”的生动写照。在基于分数的因果发现中,算法像阿峰一样在 DAG(有向无环图)空间中“爬山”:每一步只做一个微小的改动(加一条边、删一条边、转一条边),只要评分变好就接受。这种贪心策略简单高效,却经常把小山丘误认为最高峰——搜索陷入局部最优,错过评分更高的因果结构。幸运的是,我们有三类经典的“破局”策略:随机重启(换一座山重新爬)、邻域扰动(大胆跳离当前山丘)、多起点搜索(多派几支登山队分头行动)。今天这篇文章,就沿着阿峰的脚步,把局部最优的成因与逃逸策略讲个明白。
对 AI 从业者而言,理解局部最优陷阱是设计稳健结构学习算法的前提——它能帮你避免次优拓扑的固化,在构建可信 AI 基础设施时交付更可靠的因果模型。
可视化演示
展示分数景观中贪心搜索陷入局部最优、随机重启多起点覆盖与邻域扰动逃逸机制,帮助理解因果发现结构学习优化策略。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+captions.landscape+'
贪心搜索原理
阿峰的登山法则是“只往高处走”。他每走一步,就抬起脚试探四周:左边高一点,就往左;右边高一点,就往右。这个方法效率极高,因为他从不浪费时间去走“下坡路”。但它的代价也很明显——他只看得到脚边几米的地形,看不到山的那边还有更高的峰。在基于分数的因果发现中,贪心搜索就是这样一个“只往高处走”的登山者。
把这种思路搬到 DAG 结构学习里:算法从某个初始结构出发,每一步枚举当前结构的所有“邻居”——也就是只改动一条边就能得到的其他合法 DAG。它用一个评分函数(如 BIC、BDeu)给每个候选结构打分,然后毫不犹豫地走向评分最高的那个邻居。评分函数的角色正如阿峰手中的海拔仪,它不关心结构是否“看起来合理”,只关心数学上对数据的解释能力是否更强。
这种“每步取最优”的搜索策略,数学上可以写成如下形式:
阿峰靠着“只看脚边”的规则很快登上了眼前的小丘,但也正是这条规则锁死了他的视野。贪心搜索的高效与短视,就像这枚硬币的两面——他从不走弯路,也因此永远看不到弯路之后可能藏着的大山。
- 当前位置 \(x_t\)表示第 \(t\) 步搜索到的 DAG 结构,好比阿峰此时此刻站立的地点。
- 邻域 \(\mathcal{N}(x_t)\)从当前结构出发,通过一次加边、删边或转边能到达的所有合法 DAG 结构。
- 候选评分 \(S(x')\)对每一个邻居结构用评分函数打分,分数越高表示越符合数据。
- 取最大 \(\arg\max\)在所有邻居中挑选评分最高的一个,作为下一步移动的目标。
局部最优成因
阿峰站在小丘上,疑惑地转了一圈:东边比这里低,西边也比这里低,南边北边都低!他郑重地宣布“我到了最高峰”——但这恰恰是问题的核心:他只在“附近”做了比较。在 DAG 结构搜索里,这个“附近”被严格定义为“单步操作可达的邻居集合”。一个结构如果评分比所有邻居都高,但比某个远处的结构低,它就是局部最优。
为什么评分景观里会有这么多“小丘”?首先,评分函数在结构空间中的取值非常崎岖——加一条边可能让分数暴涨,再加第二条又暴跌,形成无数坑坑洼洼的局部隆起。其次,DAG 空间是天文数字级的:\(n\) 个节点的 DAG 数量随 \(n\) 指数爆炸,哪怕只有 10 个节点,可能的结构也是亿级以上。阿峰在如此广阔的山脉里,凭一条“只往上走”的规则,几乎没有可能恰好走到真正的最高峰。
更隐蔽的原因是路径依赖。阿峰一开始选择往东南走,后来发现西边有更高的山,但他已经下了太多坡,按规则“不能走下坡路”,就永远回不去了。贪心搜索同样如此——早期的一个错误边决策,会让后续所有操作都围绕错误结构修修补补,最终停在一个次优的拓扑上。数学上,局部最优的严格定义是:
阿峰终于明白:山丘的“高”是相对于脚边而言的,山外有山。局部最优之所以让人防不胜防,正是因为它用“邻域内的绝对优势”掩盖了“全局范围内的相对劣势”。
- 局部最优 \(x^*\)一个在邻居眼中“最强”的 DAG 结构,好比阿峰脚下的小山丘。
- 邻域无敌对邻域中的每一个结构 \(x\),它的评分都不高于 \(x^*\)。
- 全局不如但与真正的全局最优相比,仍然差了一截。
登山搜索过程
如果把贪心搜索放大来看,它其实是一套非常机械的操作流程。阿峰的登山之道可以被拆解为“初始化—试探—判断—停滞”四个环节:他先随便找个起点站好,然后反复试探“往哪走会变高”,一旦找不到更高的方向,就原地扎营,宣告结束。DAG 搜索的登山算法(Hill Climbing)也是这个套路。
第一步是初始化——从空图、随机图或基于先验知识的图出发。第二步是生成邻域——枚举所有“单边操作”,包括加边(添加一条原本不存在的有向边)、删边(去掉一条已有边)、转边(把一条边的方向反转)。每一次操作必须保证不产生有向环,这是 DAG 结构合法性的底线,就像阿峰不能踩着沼泽上山一样。第三步是评分判断——对每个候选结构算一遍评分,看分数有没有提升。最后,当所有邻居的评分都不再高于当前结构时,搜索陷入停滞:
阿峰把每一步拆成“试探—比较—迈步”,每一步都走得有理有据。然而登山者走得越“理性”,就越容易在一座小丘上彻底驻足——因为在他严苛的规则里,任何下坡都是不被允许的。
- 当前结构 \(G\)搜索进行到现在所在的 DAG,相当于阿峰脚下的位置。
- 候选结构 \(G'\)对当前结构做一次加边、删边或转边操作后得到的新 DAG。
- 评分差 \(\Delta S\)用评分函数分别打分再相减,度量“这一步值不值得走”。
- 严格提升才迈步只有评分为正才接受移动,否则留在原地。
随机重启策略
站在小丘上,阿峰终于意识到问题:不是自己没有能力,而是起步的位置决定了很多。如果一开始就往西边那座更高的山走,结果会完全不同。于是他做出一个大胆的决定——下山,换一座山重新爬。一次不行,就再来一次。他把这个策略称为“多试几座山”。
随机重启策略(Random Restart Hill Climbing)的核心思想正是如此:不再执着于从同一个起点一路爬到头,而是反复地从随机生成的初始结构出发,每次独立执行完整的贪心爬山,最后保留所有登山结果中评分最高的那个。这里的“随机初始点”可以理解为阿峰每次被直升机空投到山脉的不同区域,每个区域的地形都不同,撞见真正最高峰的概率自然大大增加。
假设单次搜索找到全局最优的概率是 \(p\),那么独立重复 \(k\) 次后,至少成功一次的概率呈指数增长:
阿峰试了 3 座山、5 座山、10 座山,果然在第 7 次时看到了那座巍峨的真正的最高峰。但他的新烦恼也随之而来:每次重启都要从头爬一遍,时间成本越来越高。随机重启不是免费的午餐,而是用“时间”换“概率”的一场赌局。
- 单次成功概率 \(p\)从某一次随机初始点出发爬山最终找到全局最优的概率,通常很小但大于 0。
- 单次失败概率 \(1-p\)这一次尝试没能找到全局最优的概率。
- \(k\) 次全部失败因为每次搜索相互独立,所有尝试都失败的概率是相乘关系。
- 至少成功一次用“必然事件概率 1”减去“全部失败”,即得到至少一次成功的概率。
邻域扰动机制
“重启”听起来很美好,但每次都要从零开始,效率太低了。阿峰心想:我现在已经在一座山上了,为什么不直接往山下跳几大步,落到另一座山的半山腰再往上爬呢?这个想法,对应着结构学习中的另一类强大策略——邻域扰动,其中最著名的代表就是模拟退火算法。
模拟退火的关键在于:允许算法在温度较高时“接受差解”。想象阿峰在高温下像个弹力球,就算前方的路是下坡,他也可能因为“热运动”而滚过去;随着温度逐渐降低,他越来越安静,最终只会老老实实地向高处走。翻译成算法语言就是:当候选结构 \(G'\) 的评分比当前结构 \(G\) 差时,不再像贪心搜索那样直接拒绝,而是以一定概率接受这次“下坡移动”。这个接受概率由温度 \(T\) 控制:
随着退火进程推进,温度从初值 \(T_0\) 按冷却系数逐渐衰减,阿峰的“弹跳能量”越来越小,最终收敛并稳定在某座山峰上。邻域扰动的精髓就在于此:先靠高温度过山丘低谷,再靠低温锁定真正的高峰。它给了贪心搜索一个“回头”的机会,而这一回头,往往就能看见被山丘遮挡的辽阔风景。
- 评分差 \(\Delta S\)候选结构评分减去当前结构评分,此时为负值,代表这是一条“下坡路”。
- 温度 \(T\)一个正的控制参数,好比阿峰身上的“弹跳能量”,温度越高越敢走不寻常的路。
- 接受概率\(\Delta S\) 除以 \(T\) 后取指数,得到一个介于 0 和 1 之间的概率值。
多起点搜索
阿峰的经验在登山队里传开了。这一次,他不再单兵作战,而是召集了多支登山队:队长老赵从南坡出发,队员小陈从北坡出发,向导老刘则根据旧地图先标记了几个疑似高峰的坐标作为起点。大家分头行动,最后在山脚集合,汇报各自找到的最高点,取其中最高者作为全队成绩。这就是多起点搜索(Multi-start Search)的思路。
与随机重启相比,多起点搜索更强调初始点的“设计感”。随机重启的初始点完全随机,靠“碰运气”式的大量尝试来提高成功率;而多起点搜索则会刻意安排多样化的初始结构——比如一个空图(从最简模型开始)、一个随机图(探索未知区域)、一个基于专家先验的图(从可信的局部区域开始)、甚至先用贪心快速爬出一个结果作为另一个起点。这样一来,不同的“登山队”覆盖了山脉的不同区域,最终找到全局最优的可能性大大提升。
多起点搜素最终的选择逻辑非常直白——把所有队伍带回的最终解放在一起,用评分函数做终极排名,选出王者:
登山队最终找到的那座最高峰,不是靠一个人闷头苦爬,而是靠分工与多样才赢来的。多起点搜索和随机重启、邻域扰动一样,都在回答同一个问题:在你不知道全局地形的时候,怎样提高找到最高峰的可能性? 答案是——从不同的视角多试几次。
- 结果集合 \(\mathcal{R}\)各支登山队带回的全部最终结构,即各条搜索路径的终点。
- 逐个体检对集合中每一个最终解 \(G\) 计算评分函数 \(S(G)\)。
- 终极排名在所有最终解中选出评分最高者,作为多起点搜索的输出。
AI 应用场景
基因调控网络重构
阶段:模型训练与搜索优化
解决的问题:从基因表达数据推断基因调控网络,本质上是基于分数的因果结构学习。传统的贪心搜索容易陷入局部最优,导致识别出的调控关系偏离真实生物学通路。通过随机重启与邻域扰动,搜索可以跳出次优拓扑,找到与实验数据更吻合的调控结构。
具体案例:某癌症研究团队用多起点搜索配合贪心爬山,从单细胞 RNA 测序数据中识别与肿瘤转移相关的基因调控模块。相比纯贪心,最终结果在已知通路数据库中的命中率提升了约 35%,并发现了 3 条此前被遗漏的调控边。
金融风险传导分析
阶段:可解释 AI 与因果推断
解决的问题:金融机构需要识别风险因子之间的因果传导链,而非仅仅做相关性预测。贪心结构学习容易过早收敛到小规模子图,漏掉“流动性紧张 → 债务违约 → 股价下跌”这类长链条因果路径。邻域扰动和随机重启能帮助算法跳出短链陷阱。
具体案例:某风控团队利用模拟退火策略,从企业财务指标时间序列中推断多级风险传导的 DAG 结构,发现了一条此前模型从未捕捉到的间接因果链,并将其加入风控规则库,提升了信用风险预警的召回率。
推荐系统因果纠偏
阶段:特征工程与模型去偏
解决的问题:推荐系统的观测数据存在“曝光偏差”,热门物品被过度曝光导致模型误以为“曝光多=用户真爱”。用因果结构学习识别“用户偏好 → 曝光 → 点击”之间的因果方向,需要对多个候选 DAG 做评分比较,极易陷入局部最优。
具体案例:某电商平台使用多起点搜索,设计了“空图、曝光图、偏好先验图”三类初始结构并行爬山,最终估计出的因果 DAG 被用于指导训练样本的倾向性加权,显著缓解了热门物品的头部效应。
Python 演示
import mathimport random class DAG: """用邻接矩阵表示有向无环图""" def __init__(self, n): self.n = n self.adj = [[0] * n for _ in range(n)] def copy(self): g = DAG(self.n) for i in range(self.n): for j in range(self.n): g.adj[i][j] = self.adj[i][j] return g def add_edge(self, i, j): if i != j and self.adj[i][j] == 0 and not self.has_path(j, i): self.adj[i][j] = 1 return True return False def remove_edge(self, i, j): if self.adj[i][j] == 1: self.adj[i][j] = 0 return True return False def has_path(self, s, t): """深度优先判断从 s 到 t 是否存在有向路径""" visited = [False] * self.n stack = [s] while stack: u = stack.pop() if u == t: return True for v in range(self.n): if self.adj[u][v] == 1 and not visited[v]: visited[v] = True stack.append(v) return False def neighbors(self): """枚举加边、删边、转边三类操作得到的所有合法邻居""" nbrs = [] for i in range(self.n): for j in range(self.n): if i == j: continue if self.adj[i][j] == 0: g = self.copy() if g.add_edge(i, j): nbrs.append(g) else: g = self.copy() g.remove_edge(i, j) nbrs.append(g) g2 = self.copy() g2.remove_edge(i, j) if g2.add_edge(j, i): nbrs.append(g2) return nbrs def bic_score(g, data): """极简 BIC 评分: 边数惩罚 + 数据拟合收益""" n = g.n edge_count = 0 fit = 0.0 for i in range(n): for j in range(n): if g.adj[i][j] == 1: edge_count += 1 fit += data[i][j] return fit - 0.5 * edge_count def random_init(n): """随机生成初始 DAG, 每条边以 20% 概率尝试加入""" g = DAG(n) for i in range(n): for j in range(n): if i != j and random.random() < 0.2: g.add_edge(i, j) return g def hill_climb(data, n, start=None, max_steps=100): """贪心爬山: 每次移到评分最高的邻居, 直到无法提升""" cur = start if start is not None else DAG(n) score = bic_score(cur, data) for _ in range(max_steps): best_g, best_s = None, score for nxt in cur.neighbors(): ns = bic_score(nxt, data) if ns > best_s: best_s, best_g = ns, nxt if best_g is not None: cur, score = best_g, best_s else: break return cur, score def random_restart(data, n, k=5): """随机重启: k 次独立随机起点贪心爬山, 保留全局最优""" best_g, best_s = None, float('-inf') for _ in range(k): g, s = hill_climb(data, n, start=random_init(n)) if s > best_s: best_g, best_s = g, s return best_g, best_s def simulated_annealing(data, n, T0=2.0, alpha=0.98, max_iter=500): """模拟退火: 以一定概率接受差解, 跳出局部最优""" cur = DAG(n) score = bic_score(cur, data) best_g, best_s = cur.copy(), score T = T0 for _ in range(max_iter): nbrs = cur.neighbors() if not nbrs: break nxt = random.choice(nbrs) ns = bic_score(nxt, data) delta = ns - score if delta > 0 or random.random() < math.exp(delta / T): cur, score = nxt, ns if score > best_s: best_g, best_s = cur.copy(), score T *= alpha if T < 1e-4: break return best_g, best_s def main(): random.seed(42) n = 4 # 模拟真实因果结构带来的拟合收益: 0->1, 0->2, 1->3 是强关系 data = [[0.0] * n for _ in range(n)] data[0][1] = 2.0 data[0][2] = 1.8 data[1][3] = 2.2 data[0][3] = 0.6 g1, s1 = hill_climb(data, n) g2, s2 = random_restart(data, n, k=5) g3, s3 = simulated_annealing(data, n) print("贪心爬山 边数:", sum(sum(row) for row in g1.adj), "评分:", round(s1, 2)) print("随机重启 边数:", sum(sum(row) for row in g2.adj), "评分:", round(s2, 2)) print("模拟退火 边数:", sum(sum(row) for row in g3.adj), "评分:", round(s3, 2)) if __name__ == "__main__": main()
def neighbors(self):生成当前 DAG 的全部合法邻居,对应登山的“试探脚边每一步”if self.adj[i][j] == 0:判断 i 到 j 之间是否已有边,决定当前是加边还是删边分支g2.add_edge(j, i)尝试把边 i→j 转成 j→i,即“转边”操作return fit - 0.5 * edge_count极简 BIC 评分:结构越简单加分越少,拟合越好加分越多def hill_climb(data, n, start=None, max_steps=100):贪心爬山主函数,反复进入评分最高的邻居if ns > best_s:严格大于才更新最优候选,保证每一步都“只往高处走”def random_restart(data, n, k=5):随机重启主函数,k 次独立爬山取全局最优g, s = hill_climb(data, n, start=random_init(n))从随机生成的初始结构开始一次完整爬山if delta > 0 or random.random() < math.exp(delta / T):模拟退火的接受准则:变好必接受,变差按概率接受
常见误区
误区我以为局部最优就是指搜索到的结构评分很低、分数不理想,只要评分高就是全局最优。
正解局部最优是“相对”概念——它所在的邻域内确实评分最高,但全局还有更优解。就像阿峰脚下的小土丘,在周围几公里内确实是最高点,评分值也不算低,但远处的高山才是真正的目标。一个局部最优的评分可以很高,只是还不够高。
为何常见:把“局部”理解成“差劲”,忽略了“邻域”这个关键限定范围,导致判断标准错位。
误区既然一次重启可能失败,那我重启 100 次甚至 1000 次,效果一定越来越好。
正解成功率随重启次数是指数逼近 1 的,收益边际递减。从 10 次提升到 100 次,成功率从约 89% 提升到接近 100%,但耗时也涨了 10 倍。实际工程里要在计算预算与质量之间做权衡,通常会结合扰动策略,而不是无限堆次数。
为何常见:只看到成功概率的上升,忽略了时间成本这条同样在增长的曲线。
误区模拟退火会接受差解,所以它肯定比贪心强,一定能找到全局最优。
正解理论上只有“无限慢”降温才能保证收敛到全局最优;实际有限迭代只是“更可能”接近全局最优。温度初值、冷却系数、邻域设计都会强烈影响结果,调参不当甚至比单纯贪心还差。
为何常见:把理论收敛性等同于实际性能保证,忽视了有限时间下的工程折中。
误区贪心容易陷入局部最优,那就应该彻底抛弃贪心,直接用复杂的模拟退火或多起点搜索。
正解贪心是几乎所有高级策略的“地基”——随机重启和多起点搜索本质上都是“多次贪心”,模拟退火在温度趋近于 0 时也退化为贪心。贪心得到的结果经常是后续扰动的优质起点,抛弃贪心等于扔掉了最精准的局部寻优能力。
为何常见:把“贪心的缺点”放大为“贪心一无是处”,没有看到它作为基础模块的组合价值。
误区它们不都是“从多个点开始搜”吗?我觉得这俩完全是一回事。
正解随机重启的关键在“重复试错”——用大量完全随机起始点去撞全局最优,赌的是次数;多起点搜索的关键在“多样覆盖”——刻意设计差异化的初始点(空图、随机图、先验图、贪心图),让不同区域都被探索到。实现上经常结合,但设计意图差异明显。
为何常见:只看到表面形式的相似,没理解两种策略各自想解决的核心问题不同。
面试题
Q1. 贪心搜索在每一步会做什么?
- A.枚举全部可能的DAG结构
- B.选择评分提升最多的操作← 正确答案
- C.随机选择一个合法操作
- D.保留所有候选结构
答案:B。贪心搜索的核心是“每一步都选当前评分提升最大的操作”,对应阿峰只往最高处走的登山法则。A 是全空间枚举,C 是随机搜索,D 是光束搜索的思想,都不符合贪心。
Q2. 关于局部最优,下列说法正确的是?
- A.当前评分最低的DAG
- B.包含边数最多的DAG
- C.邻域最高但非全局最优← 正确答案
- D.评分等于全局的最优DAG
答案:C。局部最优是“邻域内评分最高,但不是全局最高”的结构,恰如阿峰脚下的小土丘——周围几公里它最高,但远处还有更高的山。A 把方向搞反了,B 与评分无关,D 描述的是“找到了全局最优”而非局部最优。
Q3. 在模拟退火中,“温度”的作用是什么?
- A.数据归一化范围
- B.节点数量控制
- C.边的权重赋值
- D.差解接受概率← 正确答案
答案:D。温度控制算法接受差解的概率:高温时像弹力球一样敢走“下坡路”,低温时则趋于稳定,只走高坡。A、B、C 都是干扰项,温度与评分函数、结构形态无关。
Q4. 随机重启策略的核心思想是什么?
- A.多次随机起点爬山择优← 正确答案
- B.反复修改当前解的边
- C.放弃评分改用互信息
- D.用深度优先搜索替代
答案:A。随机重启就是多次从随机初始点出发独立做贪心爬山,最终选择评分最高的结果,相当于阿峰被直升机空投到不同区域多爬几次山。B 是邻域扰动,C 换了一种评分准则,D 换了搜索策略,都不是重启的本意。
Q5. 贪心搜索陷入局部最优的根本原因是什么?
- A.评分函数计算耗时太多
- B.只接受提升且邻域有限← 正确答案
- C.初始DAG包含太多边
- D.原始数据量过于庞大
答案:B。贪心只接受评分提升的移动,且邻域只覆盖单步操作,导致它无法绕过“评分低谷”去发现更高的地方。就像阿峰坚持不走下坡路,就永远到不了被山谷隔开的那座大山。C、D 是工程问题而非结构成因。
Q6. 单次搜索找到全局最优的概率 p=0.2,随机重启 k=5 次,至少成功一次的概率约为?
- A.约 0.20
- B.约 0.33
- C.约 0.67← 正确答案
- D.约 1.00
答案:C。用公式 \(1-(1-p)^k\) 计算:\(1 - 0.8^5 = 1 - 0.32768 \approx 0.67\)。这正是随机重启“用次数换概率”的量化体现。A 是单次概率,B 是 \(0.8^5\) 的取值,D 错在假设必然成功。
Q7. 贪心搜索中的“邻域”指的是什么?
- A.所有合法DAG结构集合
- B.相同评分的结构集合
- C.随机构造的结构集合
- D.单步边操作邻居集合← 正确答案
答案:D。邻域是对当前结构做一次加边、删边或转边操作所能到达的合法 DAG 集合。A 是整个搜索空间,B 是等分线概念,C 是随机采样,都不是邻域的准确定义。
Q8. 下列哪些策略可以有效缓解局部最优问题?
- A.使用随机重启策略← 正确答案
- B.使用邻域扰动策略← 正确答案
- C.使用多起点搜索策略← 正确答案
- D.提高评分函数精度
答案:ABC。随机重启、邻域扰动(模拟退火)、多起点搜索是三大经典逃逸策略,分别从次数、跳离、多样三个角度突破局部最优。提高评分函数精度只让“海拔仪”更准,无法解决“只走高处”的路线问题。
Q9. 关于局部最优与全局最优的关系,下列说法正确的是?
- A.局部最优在邻域内评分最高← 正确答案
- B.全局最优不一定是局部最优
- C.全局最优一定是局部最优← 正确答案
- D.局部最优不一定就是全局最优← 正确答案
答案:ACD。局部最优在邻域内评分最高(A 对);全局最优在其邻域内也不可能有更高评分的邻居,否则它就不是全局最优,因此全局最优一定是局部最优(C 对、B 错);局部最优只是“邻域之王”,不一定能称霸全局(D 对)。
Q10. 关于随机重启与多起点搜索,下列说法正确的是?
- A.两者都会从多个初始结构出发← 正确答案
- B.随机重启侧重重复试错次数← 正确答案
- C.多起点侧重初始点多样性← 正确答案
- D.两者本质上是完全相同的
答案:ABC。两者都从多个初始结构出发(A 对);随机重启靠随机初始点的重复次数磨概率(B 对);多起点搜索刻意设计多样化初始点,如空图、先验图、贪心结果等,强调覆盖不同区域(C 对)。D 忽略了设计意图的本质差异。