一种绘制有向图的方法<TSE93> - 2. 最优层级分配

一种绘制有向图的方法<TSE93> - 最优层级分配

2 最优层级分配

第一步为图G中所有节点v根据其边分配一个整数层级λ(v)。这意味着每一个 e=(v, w)成员 E都存在l(e)>=δ(e),e=(v,w)的长度l(e)被定义为λ(w)− λ(v), 并且δ(e)描述了一些给定最小长度限制,δ(e)通常为1, 但可以取值为任何非负整数。技术原因δ(e)可以被内部设定在后面章节详述,也可根据用户需要外部设定调整等级分配。在这一步,任何非空集合 S m a x , S m i n , S 0 , . . . , S k S_{max}, S_{min}, S_{0}, ..., S_{k} Smax,Smin,S0,...,Sk 会被暂时合并为一个节点。环会被忽略,多边会被合并为单个边,其权重是被合并边的权重合。为了提高效率,不是以上集合中的叶子节点会被忽略,这些叶子节点的权重会被琐碎的确定为最佳等级。

2.1 消除图中的环

图必须是无环的才能获得一致的等级分配。由于输入的途中可能包含环,就需要一个预处理步骤来发现并通过翻转一些边来消除环[RDM]。这些边只会在内部被翻转;箭头在绘制时还会按照原本的方向。有效的消环方法可以基于深度优先搜索。边会根据输入的图从某些来源节点或者目标节点以"自然顺序"被搜索。深度优先搜索会将边分为两类: 树的边或者非树的边[AHU]。树定义了节点的部分有序。基于这种部分有序,非树边进一步被分为了三类: 交叉边(cross edge),前向边(forward edge),后向边(back edge)。交叉边按部分顺序连接无关的节点。前向边连接节点和他的一些后代。后向边连接了节点和它的祖先。显然前向边和交叉边在部分有序中不会创建环。通过翻转后向边,使它们成为前向边,这种方式可以消除所有的环。

尝试翻转最少的一组边看似合理。但困难在于找到最小集("反馈弧集"问题)是NP完全的[EMW][GJ]。更重要的是这可能并不能改善绘图的效果。我们实现了启发式算法来翻转同时参与多个环的边。启发式算法以任意顺序一次获取一个非平凡的强连接组件。对于每个组件,计算那个边沿深度优先遍历形成环的次数。最大计数的边将会被翻转。不断重复该过程,知道不再有这样非平凡连接组件为止。

对这种启发式算法进行的实验表明,即使包含环,实际应用中大多数有向图也具有自然的总边缘方向。图输入通常会呈现这种自然的方向性。翻转不恰当的边会干扰正常的绘制。例如,即使过程调用图具有循环,仍然希望在图的顶部而不是中间看到顶层函数。从稳定性角度来看,深度优先,启发式破环方法似是最优选择。如多数研究人员[Ca] [Ro] [STT]建议的那样,与通过将一个循环中的所有节点折叠成一个节点,或将一个循环中的节点置于同一等级,或复制该循环中的一个节点相比,该方法还可以提供更多的信息。

另一个细节在于代表 S m a x 和 S m i n S_{max}和S_{min} SmaxSmin的节点必须始终具有最大和最小的等级分配。通过翻转 S m a x 和 S m i n S_{max}和S_{min} SmaxSmin的出边来确保此属性。同样对于所有没有入边的节点v,设定一个临时边 ( S m i n , v ) (S_{min}, v) (Smin,v),令其δ=0。对于所有的没有出边的节点v,设定一个临时边 ( v , S m a x ) (v, S_{max}) (v,Smax),令其δ=0。于是,对于所有的v存在 λ ( S m i n ) ≤ λ ( v ) ≤ λ ( S m a x ) λ(S_{min}) ≤ λ(v) ≤ λ(S_{max}) λ(Smin)λ(v)λ(Smax)

2.2 问题定义

原则A3规定设定短边。除了制作更好的布局之外,短边还能缩短取决于总边长的后遍的运行时间。因此,需要找到一个最优的节点等级,即所有加权边缘长度之和最小的节点等级。可以被描述为以下的整数程序:

m i n ∑ ( v , w ) m e m b e r E ω ( v , w ) ( λ ( w ) − λ ( v ) ) s u b j e c t   t o : λ ( w ) − λ ( v ) ≥ δ ( v , w ) ∀ ( v , w )   m e m b e r E \begin{aligned} &min\sum_{(v,w)member E} \omega(v,w)(\lambda(w) - \lambda(v))\\ &subject\ to: \lambda(w) - \lambda(v) \geq \delta(v, w) \forall(v,w)\ memberE \end{aligned} min(v,w)memberEω(v,w)(λ(w)λ(v))subject to:λ(w)λ(v)δ(v,w)(v,w) memberE

如前所述,权重函数ω和最小长度函数δ分别将边缘集合E映射为非负有理数和非负整数。有多种方法可以在多项式时间内求解此整数程序。一种方法是求解等价线性程序,在多项式时间内将其转换为整数。另一种方法涉及将最优等级分配问题转换为最小成本流动或循环问题,词类问题存在多项式时间算法(参考[GT]及其参考)。由于约束矩阵是完全单模的,因此通过单纯型算法也可以解决问题,尽管不一定在多项式时间内。[GNV2]中将对这些技术进行更多讨论。

2.3 网络单纯型(Network simplex)

这里我们叙述最简单的基于网络单纯型算法的公式[Ch]。尽管该方法的时间复杂度未被证明为是多项式的,在实践中确实只需要很少的迭代并能够快速运行。

这里从一些定义和观察开始。一种可能的排序方式是对于所有的e满足长度约束 l ( e ) ≥ δ ( e ) l(e) \geq \delta(e) l(e)δ(e)。对于任何给定的等级,不是必须可行,边的松弛度在于它的长度和最小长度之间的区别。从而,当所有边的松弛度都为非负时该排序就是可用的。如果一个边的松弛度为0,该边就是绷紧的。

图的生成树会包含等级,或者说是一个等效等级系列。(注意,生成树可能基于无根无向的图,并且也不必须是一个有向的树)这个排名是通过选择一个初始节点并为其分配等级来生成的。然后,对于在生成树中与分配的节点相邻的每个节点,为其分配相邻节点的等级,根据它是连接边缘的头还是尾递增或递减,边缘的最短长度。 继续此过程,直到所有节点都已排名。 如果生成树促成可行的排序,则它是成立的。 通过构造,可行树中的所有边都是紧的。

给定一个可行的生成树,可以将整数切值(cut value)与每个树边相关联,如下所示。如果删除树边,则树分为两个连接的组件,尾部组件包含了该边的尾部节点,头部组件包含了头部节点。切值定义为从尾部组件到头部组件所有的边的权重之和,包含树边,减去从头部到尾部所有边的权重之和。

通常(但非总是由于退化导致),负数割值(cut value)表示可以通过尽可能的延长树边长度来减小加权边缘总长度,直到头部组件到尾部组件边边缘之一被绷紧为止。这对应于用新的紧树边替换生成树中的树边,从而获得新的可行生成树。显而易见最佳排名可以用于生成由可行生成树引起的另一个最佳排名。这些观察结果是解决图型而不是代数上下文中排名问题的关键。负数切值(cut value)的树边会被替换为适当的非树边,直到所有的树边都具有非负的切值。为了保证终止,该实现采用反循环技术,尽管在实践中从未发现有此必要。返回的生成树对应最佳的排名。为了进一步讨论网络单纯形算法的终止和结果最优性,有兴趣的读者可以参考文献[Ch][Cu][GNV2]

如图2-1 下面描述了该实现版本的网络单纯形算法

procedure rank()
	feasible_tree();
	while(e = leave_edge()) != nil do
		f = enter_edge(e);
		exchange(e, f);
	end
	normalize();
	balance();
end
图 2-1. 网络单纯形

标注如图 2-1所示.

2: 方法feasible_tree构建初始可行生成树。该过程会被更多简单描述如下。单纯形法从可行的解决方案开始,并保持这一不变性。

3: leave_edge返回具有负数切值的树边,如果没有则返回nil,意味着该方案已经最优。任何具有负数切值的树边都会被作为选中的边移除。

4: enter_edge寻找非树边来替代e。 通过断开边e,分隔树为头部和尾部组件。所有从头部到尾部的边都被考量,最终会选择一条具有最小松弛度的边。这是维持可行性的必要条件。

5: 边一旦被交换,更新树以及切值。

7: 通过将最小等级设置为零来标准化该方案。

8: 具有相等入边和出边权重以及多可行等级的节点将被移动到节点最少的可行等级。目标是根据原理A4减少拥挤并提高图形的纵横比。调整不会更改等级分配的成本。节点以贪婪的方式进行调整,效果很好。在即将发表的论文[GNV2]中考虑了整体平衡等级。

procedure feasible_tree()
	init_rank();
	while tight_tree() < |V| do
		e = 树上的最小松弛量的非树边
		delta = slack(e);
		if 相关节点是e.head then delta = -delta;
		for v in Tree do v.rank = v.rank + delta;
	end
	init_cutvalues();
end
图 2-2. 寻找初始可行树

标注图2-2所示:

2: 初始可行等级被计算出来。为了简洁起见, init_rank未在此给出。我们的版本将节点排在队列中。 当节点没有未被扫描的入边时,将它们置于队列。当将节点从队列中移出时,将为其分配满足入边的最小等级,并且出边会被标记为已扫描。 在最简单的情况下,所有边的 δ = 1 \delta = 1 δ=1, 这相当于将图形视为一个偏序集合,并将最小元素分配给等级0。从偏序集合中移除了节点,并为新的最小元素集分配了等级1, 等等

3: 函数tigh_tree查找包含一些固定节点的紧缩边最大树,并返回树中节点数。注意,此类最大树只是子图的生成树,该子图由仅使用紧缩边的从底层的无向图中的固定节点可达的所有节点指引。通常所有这样的树具有相同数量的节点。

4-8: 这会找到与树相邻的非树节点的边,并调整树节点的等级以使该边缘变紧。 由于选择的边缘具有最小的松弛度,因此得出的排名仍然可行。 因此,在每次迭代中,最大紧密树至少获得一个节点,并且算法最终以可行的生成树终止。 该技术本质上是Sugiyama等人[STT]描述的技术。

10: init_cutvalues函数计算树边的切值。 对于每个树边,计算方法是将节点标记为属于头或尾部分,然后对头和尾位于不同部分的所有边的正负权重求和,对于那些从头部到尾部要被去掉的边,符号为负。

图 2-3. 查询优化的可行树

运行网络单纯形算法的一个小例子如图2-3所示。 非树边缘点有虚线,所有边缘的权重均为1。在图(a)中,显示了初始化级别之后,并指示出所有切值的图。 例如,边(g,h)的切值是-1,对应于边缘(g,h)的权重(从尾部分量到头部分量)减去边缘(a,e)和( a,f)(从头到尾)。 在(b)中,具有负切割值的边缘(g,h)已由非树边缘(a,e)替换,并显示了新的切割值。 因为它们都是非负的,该方案是优化的而且算法可终结。

2.4 实现细节

网络单纯形算法的版本已广为人知,并且在文献中有一些结果可帮助调整实现[Ch]。但是,我们认为有必要为预期的实现者指出几个具体要点。这些优化在这里很有用,但是当我们在第4节中再次使用网络单纯形应用于更大的图时,就变得至关重要。

计算初始可行树和初始割值通常是解决网络单纯形算法成本的重要部分。对于许多实践中的图,初始解接近最佳,只需几次迭代即可达到最终解。在粗糙的实现中,可以通过依次获取每个树边缘,将其断开,根据其属于头或尾组件来标记每个节点并执行求和来找到初始割值。这需要O(VE)时间。

为减少此成本,我们注意到,如果从可行树的叶子向内进行搜索,则可以使用边缘局部的信息来计算切割值。由于头或尾组成部分由单个节点组成,因此以其端点之一为树中的叶子来计算树边缘的割值是很简单的。现在,假设对于所有入射到给定节点上的边缘(除了一个)的切割值都是已知的,其余边缘的切割值是已知切割值的总和加上仅取决于入射到给定节点的边缘的项。

在具有已知切割值的两个树边缘以所示方向连接第三个树边缘的情况下,图2-4中的计算。其他情况也以类似方式处理。我们假设(u,w)和(v,w)的下限值是已知的。用大写字母标记的边缘代表具有给定方向的头和尾属于所示成分的所有非树边缘的集合。(u,w)和(v,w)的切值由下式给出

C ( u , w ) = ω ( u , w ) + A + C + F − B − E − D C_{(u,w)} = \omega(u, w) + A + C + F - B - E - D C(u,w)=ω(u,w)+A+C+FBED
并且

C ( v , w ) = ω ( v , w ) + L + I + D − K − J − C C_{(v, w)} = \omega(v, w) + L + I + D - K - J - C C(v,w)=ω(v,w)+L+I+DKJC
分别. 那么切值(w, x)为:

C ( w , x ) = ω ( w , x ) + G − H + A − B + L − K = ω ( w , x ) + G − H + ( C ( u , w ) − ω ( u , w ) − C − F + E + D ) + ( C ( v , w ) − ω ( v , w ) − I − D + J + C ) = ω ( w , x ) + G − H + C ( u , w ) − ω ( u , w ) + C ( v , w ) − ω ( v , w ) − F + E − I + J \begin{aligned} C_{(w,x)}&=\omega(w,x)+G-H+A-B+L-K\\ &=\omega(w,x)+G-H+(C_{(u,w)}-\omega(u,w)-C-F+E+D)+(C_{(v,w)}-\omega(v,w)-I-D+J+C)\\ &=\omega(w,x)+G-H+C_{(u,w)}-\omega(u,w)+C_{(v,w)}-\omega(v,w)-F+E-I+J \end{aligned} C(w,x)=ω(w,x)+GH+AB+LK=ω(w,x)+GH+(C(u,w)ω(u,w)CF+E+D)+(C(v,w)ω(v,w)ID+J+C)=ω(w,x)+GH+C(u,w)ω(u,w)+C(v,w)ω(v,w)F+EI+J

仅包含局部边缘信息和已知切值的表达式。 这样,通过逐步计算切值,我们可以确保每个边缘仅被检查两次。 这大大减少了计算初始切割值所花费的时间。

另一种价值优化,类似于[Ch]中论述的技术,从某个固定的根结点 v r o o t v_{root} vroot开始对树进行后续遍历, 并用后续遍历号lim(v)标注每个节点v,最小后续遍历数为low(v)搜索中的任何后代,以及达到该结点的边的父parent(v)(见图2-5)。

图2-5 后续遍历标记为(low, lim)的节点

这提供了一种廉价的方法来测试节点是否位于树边缘的头或尾部分,从而测试非树边是否在这两个部分之间交叉。例如,如果e =(u,v)是树的边并且 V r o o t V_{root} Vroot在边缘的头部分(即lim(u)<lim(v)),则节点w在树的尾部部分当且仅当low(u)≤lim(w)≤lim(u)时才为e。这些数字还可用于在网络单纯形迭代期间有效地更新树。如果f =(w,x)是进入边,则必须调整切割值的唯一边是连接树中w和x的路径中的边。该路径是通过跟随父对象从w和x返回直到达到最小公共祖先(即第一节点l使得low(l)≤lim(w),lim(x)≤lim(l))确定的。当然,交换树的边缘时也必须调整这些后置参数,但仅适用于l以下的节点。

网络单纯形对负边缘的选择也非常敏感。可以观察到,循环遍历所有树边缘,而不是每次都从树边缘列表的开头进行搜索,可以节省许多迭代。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值