“处理根s→a→b时,需删除s和a。若只禁止下一弧b→t而保留a,后缀可能选b→a→t,拼接得到s→a→b→a→t,虽然费用有限,却已经重复a。这正是简单路径和可重复游走两种接口的分界。”
一条路线可以在到达终点前绕圈,也可以到达终点后离开再回来。若这些路线也算不同答案,反复运行“禁止重复顶点”的算法就解决了另一个问题。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,得到
零权边下,不能只为每点随便选择一条满足
对弧e=(u,v),定义偏离差价
不等式来自最短距离三角约束。每条指定树弧差价为零;非树弧也可能为零,不能把“零差价”直接当成“树弧”。称所有非树弧为sidetrack,下面简称偏离弧。
偏离序列怎样决定完整游走
一条游走删去其中全部树弧后,剩下一个按先后次序排列的偏离弧序列。给定该序列,从s沿T走到第一条偏离弧的尾点,走这条弧,再沿T到下一条尾点,最后沿T到t;每一段都由T唯一确定。
不是任意偏离弧序列都合法:下一条尾点必须在当前顶点到t的树链上。合法序列与完整游走一一对应,顺序和重复次数都要保留,不能用无序集合代替。对完整游走W有望远镜恒等式
因为
直觉
把很多可能的下一次偏离装进一个堆
在当前顶点v到t的树链上,每一点都可能离开树。直接把所有下一偏离一次性加入候选,分支数可达m;输出k条就可能为尚不需要的偏离付出过多成本。
给每个v准备一个堆H(v),包含尾点在v到t树链上的所有偏离弧,以
不同v的树链共享后缀,H(v)也应该共享结构。若为每点显式复制整条树链的全部偏离,链形图上会重复保存大量相同内容。持久化路径复制让新增局部偏离后,旧H仍可用于其他起点。
两层堆的具体构造
对每个v,把从v出发的非树弧整理成局部堆:取最小差价弧作为局部根,其余弧用Floyd法线性建一个二叉堆,并作为局部根的第三类附属孩子。局部根在这里额外单独存放,是为了后面的共享结构只合并根。
再建一个完全二叉根堆
持久化插入按完全树的新位置走一条根叶路径。沿途保留较小键,把较大键继续往下送,并复制被修改节点;其他子树共享。根键不大于旧孩子,也不大于继续下送的键,所以堆序保持。最多复制
把每个根堆节点再接上其对应局部堆的剩余二叉子树,就得到H(v)。一个节点最多有根堆左孩子、右孩子和局部堆附属根三个孩子;局部子树节点只有两个孩子。每条偏离弧在单个H(v)里恰出现一次,堆序成立;跨不同版本共享节点不等于同一版本里重复放入同一弧。[1,§2.3]
例子与边界
同一图,简单路与游走的清单不同
采用Yen页的四点八弧图。朝t的树弧为0:s→a、1:a→t、3:b→t,距离为
非树弧差价为:2:s→b取一,4:a→b取一,5:b→a取一,6:s→t取三,7:t→a取四。比如弧5权一,其差价为
默认树路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,费用七,说明终点出弧不能省略。
下载程序前十二项的费用为
第五项的弧序列是(0,4,5,1),而Yen的第五条简单路径是直达弧(6),费用六。两份榜单不同不是某个算法出错,而是允许的输出对象不同。
零权环产生无限同价答案
图只有s→a权一、a→a权零、a→t权一时,每个
本页只承诺所请求的有限前k项,不承诺在存在无限低价游走时,按费用非降枚举最终会访问每个更贵游走。也不能把“第k条”误读成“第k个不同的费用值”。
同价时,下载实现固定弧ID、顶点队列次序、堆孩子次序,再用候选插入编号serial破平局。相同输入会得到相同输出序列;这不是完整弧序列的字典序。若要另一种全序,需重新说明无限同价集合上的排名是否存在,不能只换一个比较函数便假定一切不变。
显式输出为什么可能很长
一个n点圈若可以反复经过,第i条游走可能已有
对平行弧,差价相同、端点相同仍是两个不同偏离候选,必须保留弧ID。若只求距离时曾合并平行弧,这项预处理在枚举时可能已经删掉合法答案,不能沿用而不说明。
推论与应用
从堆节点生成常数个候选
把默认树路作为首个记录。对于一个已有偏离前缀P,H(v)中的节点h代表“接下来选h携带的弧e”,其中v为P最后偏离的头点;P空时v=s。此候选的总费用是
它只生成两类后继:
- 替换最后一次偏离:对h的每个堆孩子h',仍保留P,改用h'的弧;费用增加
- 追加一次偏离:将e正式加入前缀,再取
的根;费用增加这个新根的差价,也非负
h最多有三个堆孩子,加上一个追加后继,分支最多四。初始树路只有一个后继,即H(s)的根;空堆不给后继。
这形成一棵可能无限、但每点有限分支的隐式堆序树。一个合法非空偏离序列的最后弧,在相应H(v)中有唯一位置:若不是根,唯一父状态来自替换边;若是根,唯一父状态来自去掉最后偏离的追加边。因此每条合法序列恰有一个状态,反复生成不会重复或漏掉游走。
主例中的堆节点与隐式状态分开绘制。灰箭头替换最后一次偏离,蓝箭头保留它再追加下一次偏离;对应差价均非负。
排序输出与路径恢复
用另一个普通最小优先队列保存已经发现、尚未输出的状态。取出最小状态,输出它的隐式记录,再加入上述至多四个孩子。任意尚未发现的状态,在通往根的路径上都有一个已入队祖先,祖先费用不大于它;所以队列最小项确为全局下一名。
隐式记录保存“此前偏离前缀记录的编号、最后一条偏离弧、总费用”。替换时仍指向同一前缀,追加时指向当前刚输出的记录。指针总朝更早记录,故不会形成记录环;不能把物理堆节点指针误当成完整偏离前缀,因为同一节点可供不同前缀复用。
解码时沿记录指针恢复偏离序列,再插入唯一树链。若显式输出总弧数为L,另付O(L+k)时间;零边游走也要一条分帧记录。这正是输出敏感计费所要求的输出接口区别。
把预处理、选择和展开分开计费
局部堆共包含至多m条弧,建堆为O(m)。每个顶点至多进行一次持久化根堆插入,共O(n log(n+1))时间与新增节点;距离、树、根指针还需O(n)。设单目标最短路预处理时间为
空间为
下载版以二叉堆跑反向Dijkstra,保守
原论文进一步改善路径堆构造,并用Frederickson堆选择得到
终点与迁移
手算差价表,解码(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合同、可执行持久化插入及完整解码核验独立编写。