我有一个带有顶点V
和边E
的无向图。我正在寻找一种算法来识别该图中的所有循环基础。
我认为Tarjans algorithm是一个好的开始。但the reference我的目的是找到所有 周期 ,而不是 周期基础 (其中,根据定义,不能通过其他周期的结合来构建循环。)
例如,请看下面的图表:
因此,算法会有所帮助。如果有现有的实现(最好是在C#中),那就更好了!
答案 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中的每个边缘,您有两种情况:要么带来一个全新的基本周期,要么将现有的一个分割为两个。为了跟踪这两者中的哪一个,我们跟踪顶点所属的所有基本周期(使用循环字典)。
答案 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)
检测循环的标准方法是使用两个迭代器 - 对于每次迭代,一个向前移动一步,另一个向前移动。如果有一个循环,它们会在某个时刻相互指向。
这种方法可以扩展到记录所发现的循环并继续前进。