Skip to content

算法Algorithm

Eppstein的k条最短游走

Eppstein k shortest paths · Eppstein sidetrack algorithm · k shortest walks

用到终点的最短路树和非负偏离差价编码游走,通过共享偏离堆将无限候选空间改造成常数分支堆序树,按费用输出隐式记录。

一条路线可以在到达终点前绕圈,也可以到达终点后离开再回来。若这些路线也算不同答案,反复运行“禁止重复顶点”的算法就解决了另一个问题。Eppstein方法允许这样的游走,并把每条路线压缩成“什么时候离开默认最短路树”的偏离序列。

形式陈述 ​

有限k,不要求游走集合有限 ​

沿用最短路模型中的有向游走与可加费用,由求一个最小值改为请求多份不同见证。给定n≥1个顶点的有限有向图、m条有独立ID的弧、非负权,以及s、t、k≥0。输出至多k条不同的s到t有限游走,按总费用非降排列;相同费用仍分别计数,身份由完整弧ID序列确定。自环、平行弧、重复顶点和重复弧均允许。

s=t时,零边游走是一项答案,但还可能有回到s的非空游走。若图中只有有限条可行游走且少于k,则输出全部并结束。k=0无需启动搜索;若s不能到达t,返回空。

本页实现Eppstein的基础排序输出版本,不实现原论文后面的线性k选择优化。[1] 输出先采用常数个机器字的隐式记录;需要完整弧序列时另行展开,不能把长游走的打印成本藏在“输出一项”里。

最短路树与偏离差价 ​

在反图从t运行Dijkstra,得到 d[v]=δ(v,t) 及一棵朝t指向的最短路树T。删去不能到达t的顶点及相关弧;它们不可能出现在答案里。每个剩余v≠t有一条指定树弧,使沿T前进最终到达t。

零权边下,不能只为每点随便选择一条满足 w(v,u)+d[u]=d[v] 的弧:a↔b两条零边、a→t与b→t都为一时,可能选出a↔b这个圈。下载程序在反向Dijkstra的严格改进时记父弧;该弧总指向已经先结算的顶点,从而保证树链无环。

对弧e=(u,v),定义偏离差价

Δ(e)=w(e)+d[v]−d[u]≥0.

不等式来自最短距离三角约束。每条指定树弧差价为零;非树弧也可能为零,不能把“零差价”直接当成“树弧”。称所有非树弧为sidetrack,下面简称偏离弧。

偏离序列怎样决定完整游走 ​

一条游走删去其中全部树弧后,剩下一个按先后次序排列的偏离弧序列。给定该序列,从s沿T走到第一条偏离弧的尾点,走这条弧,再沿T到下一条尾点,最后沿T到t;每一段都由T唯一确定。

不是任意偏离弧序列都合法:下一条尾点必须在当前顶点到t的树链上。合法序列与完整游走一一对应,顺序和重复次数都要保留,不能用无序集合代替。对完整游走W有望远镜恒等式

w(W)=d[s]+∑e∈WΔ(e)=d[s]+∑e∈sidetracks(W)Δ(e).

因为 w(e)=Δ(e)+d[tail(e)]−d[head(e)],所有相邻端点势项相消;即使重复顶点,仍只剩d[s]−d[t]=d[s]。此后排名只需累计非负偏离差价。

直觉

把很多可能的下一次偏离装进一个堆 ​

在当前顶点v到t的树链上,每一点都可能离开树。直接把所有下一偏离一次性加入候选,分支数可达m;输出k条就可能为尚不需要的偏离付出过多成本。

给每个v准备一个堆H(v),包含尾点在v到t树链上的所有偏离弧,以 Δ 作为堆键。只先看堆根;其余弧通过堆孩子按需揭示。堆序保证孩子不比父亲便宜,兄弟之间却不必完全排序。

不同v的树链共享后缀,H(v)也应该共享结构。若为每点显式复制整条树链的全部偏离,链形图上会重复保存大量相同内容。持久化路径复制让新增局部偏离后,旧H仍可用于其他起点。

两层堆的具体构造 ​

对每个v,把从v出发的非树弧整理成局部堆:取最小差价弧作为局部根,其余弧用Floyd法线性建一个二叉堆,并作为局部根的第三类附属孩子。局部根在这里额外单独存放,是为了后面的共享结构只合并根。

再建一个完全二叉根堆 HT(v),包含v到t树链上所有非空局部堆的根。若next(v)是树后继,则从 HT(next(v)) 的旧版本插入v的局部根;没有局部根就直接复用旧版本。t也须处理自己的出弧,不能把t默认改成吸收状态。

持久化插入按完全树的新位置走一条根叶路径。沿途保留较小键,把较大键继续往下送,并复制被修改节点;其他子树共享。根键不大于旧孩子,也不大于继续下送的键,所以堆序保持。最多复制 O(log⁡(n+1)) 个节点,因为根堆只含树链上至多n个局部根。

把每个根堆节点再接上其对应局部堆的剩余二叉子树,就得到H(v)。一个节点最多有根堆左孩子、右孩子和局部堆附属根三个孩子;局部子树节点只有两个孩子。每条偏离弧在单个H(v)里恰出现一次,堆序成立;跨不同版本共享节点不等于同一版本里重复放入同一弧。[1,§2.3]

例子与边界

同一图,简单路与游走的清单不同 ​

采用Yen页的四点八弧图。朝t的树弧为0:s→a、1:a→t、3:b→t,距离为

(d[s],d[a],d[b],d[t])=(3,2,2,0).

非树弧差价为:2:s→b取一,4:a→b取一,5:b→a取一,6:s→t取三,7:t→a取四。比如弧5权一,其差价为 1+2−2=1;弧7权二,其差价为 2+2−0=4。

默认树路s→a→t费用三。只有偏离2时,得到s→b→t,费用四;偏离序列(2,5)得到s→b→a→t,费用五;序列(4,5)得到s→a→b→a→t,也是五,但已经不是简单路径。单独偏离7得到s→a→t→a→t,费用七,说明终点出弧不能省略。

下载程序前十二项的费用为

3,4,4,5,5,6,6,6,7,7,7,8.

第五项的弧序列是(0,4,5,1),而Yen的第五条简单路径是直达弧(6),费用六。两份榜单不同不是某个算法出错,而是允许的输出对象不同。

零权环产生无限同价答案 ​

图只有s→a权一、a→a权零、a→t权一时,每个 s→a→(a→a)r→t 都是不同游走,费用都为二。任意有限k都能输出k条最低费用答案,但不存在“把全部费用二的游走输出完,再转入更贵答案”的有限时刻。

本页只承诺所请求的有限前k项,不承诺在存在无限低价游走时,按费用非降枚举最终会访问每个更贵游走。也不能把“第k条”误读成“第k个不同的费用值”。

同价时,下载实现固定弧ID、顶点队列次序、堆孩子次序,再用候选插入编号serial破平局。相同输入会得到相同输出序列;这不是完整弧序列的字典序。若要另一种全序,需重新说明无限同价集合上的排名是否存在,不能只换一个比较函数便假定一切不变。

显式输出为什么可能很长 ​

一个n点圈若可以反复经过,第i条游走可能已有 Θ(ni) 条弧;完整展开前k条便可能需要 Θ(nk2) 个输出位置。隐式记录短,只表示共享了描述,不表示实际路线也短。

对平行弧,差价相同、端点相同仍是两个不同偏离候选,必须保留弧ID。若只求距离时曾合并平行弧,这项预处理在枚举时可能已经删掉合法答案,不能沿用而不说明。

推论与应用

从堆节点生成常数个候选 ​

把默认树路作为首个记录。对于一个已有偏离前缀P,H(v)中的节点h代表“接下来选h携带的弧e”,其中v为P最后偏离的头点;P空时v=s。此候选的总费用是 c(P)+Δ(e)。

它只生成两类后继:

  • 替换最后一次偏离:对h的每个堆孩子h',仍保留P,改用h'的弧;费用增加 Δ(h′)−Δ(h)≥0
  • 追加一次偏离:将e正式加入前缀,再取 H(head(e)) 的根;费用增加这个新根的差价,也非负

h最多有三个堆孩子,加上一个追加后继,分支最多四。初始树路只有一个后继,即H(s)的根;空堆不给后继。

这形成一棵可能无限、但每点有限分支的隐式堆序树。一个合法非空偏离序列的最后弧,在相应H(v)中有唯一位置:若不是根,唯一父状态来自替换边;若是根,唯一父状态来自去掉最后偏离的追加边。因此每条合法序列恰有一个状态,反复生成不会重复或漏掉游走。

主例中的堆节点与隐式状态分开绘制。灰箭头替换最后一次偏离,蓝箭头保留它再追加下一次偏离;对应差价均非负。

排序输出与路径恢复 ​

用另一个普通最小优先队列保存已经发现、尚未输出的状态。取出最小状态,输出它的隐式记录,再加入上述至多四个孩子。任意尚未发现的状态,在通往根的路径上都有一个已入队祖先,祖先费用不大于它;所以队列最小项确为全局下一名。

隐式记录保存“此前偏离前缀记录的编号、最后一条偏离弧、总费用”。替换时仍指向同一前缀,追加时指向当前刚输出的记录。指针总朝更早记录,故不会形成记录环;不能把物理堆节点指针误当成完整偏离前缀,因为同一节点可供不同前缀复用。

解码时沿记录指针恢复偏离序列,再插入唯一树链。若显式输出总弧数为L,另付O(L+k)时间;零边游走也要一条分帧记录。这正是输出敏感计费所要求的输出接口区别。

把预处理、选择和展开分开计费 ​

局部堆共包含至多m条弧,建堆为O(m)。每个顶点至多进行一次持久化根堆插入,共O(n log(n+1))时间与新增节点;距离、树、根指针还需O(n)。设单目标最短路预处理时间为 TSP,则基础排序版的时间为

TSP+O(m+nlog⁡(n+1)+klog⁡(k+1)+1),

空间为 O(m+nlog⁡(n+1)+k)。显式展开时再加O(L+k)时间。若可行游走少于k,上式中的k可换成实际输出数再加一个终止常数。

下载版以二叉堆跑反向Dijkstra,保守 TSP=O(n+(m+1)log⁡(m+2))。若采用其他已证明的最短路实现,替换的是这一项,不改变偏离表示。费用、ID和计数器装入机器字时按单位成本计;反复绕圈会增加累计费用的位数,任意长整数不能无条件视作一条机器指令。

原论文进一步改善路径堆构造,并用Frederickson堆选择得到 O(m+nlog⁡n+k) 的隐式k个最小项集合,不要求这k项已按费用排好。[1,§3] 该优化不是把上述候选二叉堆的log k直接删掉;本页没有实现或自证这一高级选择算法,也不把它的界套到排序输出代码上。

终点与迁移 ​

手算差价表,解码(4,5)与(7),指出前者重复a、后者先到t再离开。再给一条零权自环,说明有限k怎样继续得到不同的同价游走,以及为何“枚举后过滤成简单路径”可能一直停留在无限同价层。完整核验程序分别用原图上的独立k次取点队列核对费用、用实际弧序列核对解码与去重,不以有限测试代替一一对应证明。

参考资料
  • [1] David Eppstein, Finding the k Shortest Paths,作者1997-03-31预印本;正式发表于SIAM Journal on Computing28(2),1998,652–673,DOI。§2.2、Lemmas1–3为偏离恒等式,§2.3、Lemmas4–7为共享堆与路径对应,§2.4 Theorem1为排序版;§3才是进一步优化。
  • David Eppstein, k-best enumeration,2014,§2.1、§3:隐式有界分支堆选择及k最短游走。本文小图、零环边界、弧ID合同、可执行持久化插入及完整解码核验独立编写。
关系图谱18 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系