识别无向图中所有周期基的算法

时间:2009-10-22 13:17:56

标签: graph graph-theory

我有一个带有顶点V和边E的无向图。我正在寻找一种算法来识别该图中的所有循环基础。

我认为Tarjans algorithm是一个好的开始。但the reference我的目的是找到所有 周期 ,而不是 周期基础 (其中,根据定义,不能通过其他周期的结合来构建循环。)

例如,请看下面的图表:

因此,算法会有所帮助。如果有现有的实现(最好是在C#中),那就更好了!

4 个答案:

答案 0 :(得分:6)

据我所知,不仅是Brian的预感点,而且更强大的命题仍然存在:不在最小生成树中的每个边缘都会添加一个新的“基本周期”。

要看到这一点,让我们看看当你添加一个不在MST中的边E时会发生什么。让我们做最喜欢的数学方法来复杂化并添加一些符号;)调用原始图G,添加E G'之前的图形,以及添加E G'之后的图形。因此,我们需要找出“基本周期计数”如何从G'变为G'。

添加E必须至少关闭一个周期(否则E将首先出现在G的MST中)。所以显然它必须在G'中已经存在的至少一个“基本周期”。但是它增加了多个吗?

它不能添加两个以上,因为没有边可以是两个以上基本周期的成员。但如果E是两个基本周期的成员,那么这两个基本周期的“联合”必须是G'中的基本周期,所以我们再次得到周期数的变化仍为1。

因此,对于不在MST中的每个边缘,您将获得新的基本周期。所以“计数”部分很简单。找到每个基本周期的所有边缘有点棘手,但按照上面的推理,我认为这可以做到(在伪Python中):

for v in vertices[G]:
    cycles[v] = []

for e in (edges[G] \ mst[G]):
    cycle_to_split = intersect(cycles[e.node1], cycles[e.node2])
    if cycle_to_split == None:
        # we're adding a completely new cycle
        path = find_path(e.node1, e.node2, mst[G])
        for vertex on path:
            cycles[vertex].append(path + [e]) 
        cycles
    else:
        # we're splitting an existing base cycle
        cycle1, cycle2 = split(cycle_to_split, e)
        for vertex on cycle_to_split:
            cycles[vertex].remove(cycle_to_split)
            if vertex on cycle1:
                cycles[vertex].append(cycle1)
            if vertex on cycle2:
                cycles[vertex].append(cycle2)

base_cycles = set(cycles)

编辑:代码应该在图表中找到所有基本周期(base_cycles设置在底部)。假设你知道如何:

  • 找到图表的最小生成树(mst [G])
  • 找到两个列表(edges \ mst [G])
  • 之间的差异
  • 找到两个列表的交集
  • 找到MST上两个顶点之间的路径
  • 通过向其添加额外边缘(拆分功能)将一个周期拆分为两个

它主要遵循上面的讨论。对于不在MST中的每个边缘,您有两种情况:要么带来一个全新的基本周期,要么将现有的一个分割为两个。为了跟踪这两者中的哪一个,我们跟踪顶点所属的所有基本周期(使用循环字典)。

答案 1 :(得分:4)

从头顶开始,我将首先查看任何最小生成树算法(Prim,Kruskal等)。没有更多的基本周期(如果我理解正确),而不是MST中没有的边....

答案 2 :(得分:2)

以下是我的实际未经测试的 C#代码,可以找到所有这些“基本周期”:

public HashSet<List<EdgeT>> FindBaseCycles(ICollection<VertexT> connectedComponent)
{
   Dictionary<VertexT, HashSet<List<EdgeT>>> cycles =
       new Dictionary<VertexT, HashSet<List<EdgeT>>>();

   // For each vertex, initialize the dictionary with empty sets of lists of
   // edges
   foreach (VertexT vertex in connectedComponent)
       cycles.Add(vertex, new HashSet<List<EdgeT>>());

   HashSet<EdgeT> spanningTree = FindSpanningTree(connectedComponent);

   foreach (EdgeT edgeNotInMST in
           GetIncidentEdges(connectedComponent).Except(spanningTree)) {
       // Find one cycle to split, the HashSet resulted from the intersection
       // operation will contain just one cycle
       HashSet<List<EdgeT>> cycleToSplitSet =
           cycles[(VertexT)edgeNotInMST.StartPoint]
               .Intersect(cycles[(VertexT)edgeNotInMST.EndPoint]);

       if (cycleToSplitSet.Count == 0) {
           // Find the path between the current edge not in ST enpoints using
           // the spanning tree itself
           List<EdgeT> path =
               FindPath(
                   (VertexT)edgeNotInMST.StartPoint,
                   (VertexT)edgeNotInMST.EndPoint,
                   spanningTree);

           // The found path plus the current edge becomes a cycle
           path.Add(edgeNotInMST);

           foreach (VertexT vertexInCycle in VerticesInPathSet(path))
               cycles[vertexInCycle].Add(path);
       } else {
            // Get the cycle to split from the set produced before
            List<EdgeT> cycleToSplit = cycleToSplitSet.GetEnumerator().Current;
            List<EdgeT> cycle1 = new List<EdgeT>();
            List<EdgeT> cycle2 = new List<EdgeT>();
            SplitCycle(cycleToSplit, edgeNotInMST, cycle1, cycle2);

            // Remove the cycle that has been splitted from the vertices in the
            // same cicle and add the results from the split operation to them 
            foreach (VertexT vertex in VerticesInPathSet(cycleToSplit)) {
                cycles[vertex].Remove(cycleToSplit);
                if (VerticesInPathSet(cycle1).Contains(vertex))
                     cycles[vertex].Add(cycle1);
                     if (VerticesInPathSet(cycle2).Contains(vertex))
                         cycles[vertex].Add(cycle2); ;
            }
       }
   }
   HashSet<List<EdgeT>> ret = new HashSet<List<EdgeT>>();
   // Create the set of cycles, in each vertex should be remained only one
   // incident cycle
       foreach (HashSet<List<EdgeT>> remainingCycle in cycles.Values)
           ret.AddAll(remainingCycle);

       return ret;
}

Oggy's代码非常良好且清除,但我很确定它包含错误,或者是我不懂你的伪python代码:)

cycles[v] = []

不能是边列表的顶点索引字典。在我看来,它必须是列表边缘的顶点索引字典。

并且,为了增加精确度:

for vertex on cycle_to_split:

循环到分割可能是有序边缘列表,因此要通过顶点迭代它,您必须在一组顶点中将其转换。这里的订单可以忽略不计,所以这是一个非常简单的算法。

我再说一遍,这是未经测试不完整代码,但是向前迈进了一步。它仍然需要一个合适的图形结构(我使用incidency列表)和许多图表alghoritms,你可以在像Cormen这样的教科书中找到。我无法在教科书中找到FindPath()和SplitCycle(),并且非常难以在图形中的边数+顶点的线性时间内对它们进行编码。我会在这里测试时报告它们。

非常感谢Oggy!

答案 3 :(得分:0)

检测循环的标准方法是使用两个迭代器 - 对于每次迭代,一个向前移动一步,另一个向前移动。如果有一个循环,它们会在某个时刻相互指向。

这种方法可以扩展到记录所发现的循环并继续前进。