简介
有向图G(V,E),圈是一个起始节点与终止节点相同的路径,即 a->….->a。找到所有的圈可能要遍历所有的路径,这就涉及到算法性能的考虑。本文基于深度优先搜索,讨论查找所有圈的算法加速策略。
无向图的查圈算法
深度优先搜索算法是从已知节点出发,图的一种遍历算法。只要一个节点被同源两个路径访问,这两个路径则形成一个圈。因为每个节点只处理一次,所以时间与空间复杂度都是O(N)。其算法如下:
DFS(a) for undirected graph
stack.push(a)
while not stack.empty
i = stack.pop
mark i as accessed
{j} = adjacent(i)
stack.push({j where j is not accessed })
对于无向图来说,每个 i的邻节点 j ,如果已经 accessed,那么形成一个圈。注意此时并不知道圈中的每个节点是什么。因此算法要针对有向图改造,而且要记录圈中的每个节点,每个圈都不能遗漏,还要保持高性能。
有向图的查圈算法
对于有向图来说,要记录从 a 到 i 的路径,才能判断 i的邻接点j 是否形成一个圈。算法如下:
DFS(a) for directed graph
stack.push(<{a}, 0>) //采用 stack 记录路径
while not stack.empty
<set, k> = stack.top
mark set[k++] not in stack // in-stack为路径标记
if set[k] in stack
record(set[k], stack) //在stack中找到圈上所有节点
continue
mark set[k] in stack // set[k]为当前路径上的节点
if k>|set|
stack.pop
else
stack.push(<adjacent(set[k]), 0>)
这个算法遍历了所有从 a 出发的路径,满足了查找圈的功能,不会遗漏任何一个圈。然而要穷举所有路径,这个算法通常会很慢,需要采用一些加速策略。
查圈算法的加速策略
通常的策略是对图的简化,各种情况如下:
- 0入度、0出度的节点:直接消除。因为所有在圈上的节点的入度和出度数都大于1。这是一个迭代的过程,因为消除一个可能产生另外一个。如果与邻接点形成一个圈,记录下来,直接消除。
- 相邻的1入度节点和1出度节点:合并。因为通过其中一个节点必通过另外一个节点。
- 特殊情况,2度节点,即1入1出的节点。可以形成一个长链,合并成一个节点;也可能与另外节点形成一个圈,直接记录下来。
另一个策略是一边查找,一边合并在一个圈上的节点。节点在一个圈上,这是一个等价关系。合并节点可以提高圈套圈情况下的查找性能;还可以混合使用各种查圈算法。例如,无向图的查圈算法速度不错,但是会遗漏一些圈,因此可以放在有向图的查圈算法之前执行。
还有一种策略是对图建立一系列简化图,即建立简化映射,然后对简化图进行查圈操作。当查圈操作和其它应用操作需要混合处理时,这种策略的算法更清晰。
开发加速策略,需要注意两点:
- 关注有向图的统计信息,采取有针对性地加速策略。
- 对于大型图,很难单步调试。需要自动检测一些约束条件来保证操作的正确性,这方面可参照“最弱前置条件”。
结语
这个问题没有查到更好的解决方案,主要采用图简化策略来加速算法,结果还不错。