cycle detector
parent
cb60c4ebd3
commit
9a14461a2f
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
@ -12,8 +13,21 @@ namespace Encompass
|
||||||
|
|
||||||
public class DirectedGraph<T>
|
public class DirectedGraph<T>
|
||||||
{
|
{
|
||||||
|
private class SimpleCycleComparer : IEqualityComparer<IEnumerable<T>>
|
||||||
|
{
|
||||||
|
public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
|
||||||
|
{
|
||||||
|
return Enumerable.SequenceEqual(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(IEnumerable<T> obj)
|
||||||
|
{
|
||||||
|
return obj.Aggregate(0, (current, next) => current.GetHashCode() ^ next.GetHashCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected List<T> _vertices = new List<T>();
|
protected List<T> _vertices = new List<T>();
|
||||||
protected Dictionary<T, List<T>> _edges = new Dictionary<T, List<T>>();
|
protected Dictionary<T, List<T>> _neighbors = new Dictionary<T, List<T>>();
|
||||||
|
|
||||||
public IEnumerable<T> Vertices { get { return _vertices; } }
|
public IEnumerable<T> Vertices { get { return _vertices; } }
|
||||||
|
|
||||||
|
@ -26,7 +40,7 @@ namespace Encompass
|
||||||
if (!VertexExists(vertex))
|
if (!VertexExists(vertex))
|
||||||
{
|
{
|
||||||
_vertices.Add(vertex);
|
_vertices.Add(vertex);
|
||||||
_edges.Add(vertex, new List<T>());
|
_neighbors.Add(vertex, new List<T>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,14 +59,25 @@ namespace Encompass
|
||||||
|
|
||||||
public void RemoveVertex(T vertex)
|
public void RemoveVertex(T vertex)
|
||||||
{
|
{
|
||||||
|
var edgesToRemove = new List<Tuple<T, T>>();
|
||||||
|
|
||||||
if (VertexExists(vertex))
|
if (VertexExists(vertex))
|
||||||
{
|
{
|
||||||
foreach (var u in Neighbors(vertex))
|
foreach (var entry in _neighbors)
|
||||||
{
|
{
|
||||||
RemoveEdge(vertex, u);
|
if (entry.Value.Contains(vertex))
|
||||||
|
{
|
||||||
|
edgesToRemove.Add(Tuple.Create(entry.Key, vertex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var edge in edgesToRemove)
|
||||||
|
{
|
||||||
|
RemoveEdge(edge.Item1, edge.Item2);
|
||||||
}
|
}
|
||||||
|
|
||||||
_vertices.Remove(vertex);
|
_vertices.Remove(vertex);
|
||||||
|
_neighbors.Remove(vertex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +85,7 @@ namespace Encompass
|
||||||
{
|
{
|
||||||
if (VertexExists(v) && VertexExists(u))
|
if (VertexExists(v) && VertexExists(u))
|
||||||
{
|
{
|
||||||
_edges[v].Add(u);
|
_neighbors[v].Add(u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,14 +99,14 @@ namespace Encompass
|
||||||
|
|
||||||
public void RemoveEdge(T v, T u)
|
public void RemoveEdge(T v, T u)
|
||||||
{
|
{
|
||||||
_edges[v].Remove(u);
|
_neighbors[v].Remove(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<T> Neighbors(T vertex)
|
public IEnumerable<T> Neighbors(T vertex)
|
||||||
{
|
{
|
||||||
if (VertexExists(vertex))
|
if (VertexExists(vertex))
|
||||||
{
|
{
|
||||||
return _edges[vertex];
|
return _neighbors[vertex];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -226,5 +251,163 @@ namespace Encompass
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IEnumerable<T>> SimpleCycles()
|
||||||
|
{
|
||||||
|
Action<T, HashSet<T>, Dictionary<T, HashSet<T>>> unblock = null;
|
||||||
|
unblock = (T thisnode, HashSet<T> blocked, Dictionary<T, HashSet<T>> B) =>
|
||||||
|
{
|
||||||
|
var stack = new Stack<T>();
|
||||||
|
stack.Push(thisnode);
|
||||||
|
|
||||||
|
while (stack.Count > 0)
|
||||||
|
{
|
||||||
|
var node = stack.Pop();
|
||||||
|
if (blocked.Contains(thisnode))
|
||||||
|
{
|
||||||
|
blocked.Remove(thisnode);
|
||||||
|
if (B.ContainsKey(node))
|
||||||
|
{
|
||||||
|
foreach (var n in B[node])
|
||||||
|
{
|
||||||
|
if (!stack.Contains(n))
|
||||||
|
{
|
||||||
|
stack.Push(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
B[node].Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
List<List<T>> result = new List<List<T>>();
|
||||||
|
var subGraph = Clone();
|
||||||
|
|
||||||
|
var sccs = new Stack<IEnumerable<T>>();
|
||||||
|
foreach (var scc in StronglyConnectedComponents())
|
||||||
|
{
|
||||||
|
sccs.Push(scc);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sccs.Count > 0)
|
||||||
|
{
|
||||||
|
var scc = new Stack<T>(sccs.Pop());
|
||||||
|
var startNode = scc.Pop();
|
||||||
|
var path = new Stack<T>();
|
||||||
|
path.Push(startNode);
|
||||||
|
var blocked = new HashSet<T>();
|
||||||
|
blocked.Add(startNode);
|
||||||
|
var closed = new HashSet<T>();
|
||||||
|
var B = new Dictionary<T, HashSet<T>>();
|
||||||
|
var stack = new Stack<Tuple<T, Stack<T>>>();
|
||||||
|
stack.Push(Tuple.Create(startNode, new Stack<T>(subGraph.Neighbors(startNode))));
|
||||||
|
|
||||||
|
while (stack.Count > 0)
|
||||||
|
{
|
||||||
|
var entry = stack.Peek();
|
||||||
|
var thisnode = entry.Item1;
|
||||||
|
var neighbors = entry.Item2;
|
||||||
|
|
||||||
|
if (neighbors.Count > 0)
|
||||||
|
{
|
||||||
|
var nextNode = neighbors.Pop();
|
||||||
|
|
||||||
|
if (nextNode.Equals(startNode))
|
||||||
|
{
|
||||||
|
var resultPath = new List<T>();
|
||||||
|
foreach (var v in path)
|
||||||
|
{
|
||||||
|
resultPath.Add(v);
|
||||||
|
}
|
||||||
|
result.Add(resultPath);
|
||||||
|
foreach (var v in path)
|
||||||
|
{
|
||||||
|
closed.Add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!blocked.Contains(nextNode))
|
||||||
|
{
|
||||||
|
path.Push(nextNode);
|
||||||
|
stack.Push(Tuple.Create(nextNode, new Stack<T>(subGraph.Neighbors(nextNode))));
|
||||||
|
closed.Remove(nextNode);
|
||||||
|
blocked.Add(nextNode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neighbors.Count == 0)
|
||||||
|
{
|
||||||
|
if (closed.Contains(thisnode))
|
||||||
|
{
|
||||||
|
unblock(thisnode, blocked, B);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var neighbor in subGraph.Neighbors(thisnode))
|
||||||
|
{
|
||||||
|
if (!B.ContainsKey(neighbor))
|
||||||
|
{
|
||||||
|
B[neighbor] = new HashSet<T>();
|
||||||
|
}
|
||||||
|
B[neighbor].Add(thisnode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.Pop();
|
||||||
|
path.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subGraph.RemoveVertex(startNode);
|
||||||
|
var H = subGraph.SubGraph(scc.ToArray());
|
||||||
|
var HSccs = H.StronglyConnectedComponents();
|
||||||
|
foreach (var HScc in HSccs)
|
||||||
|
{
|
||||||
|
sccs.Push(HScc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Distinct(new SimpleCycleComparer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectedGraph<T> Clone()
|
||||||
|
{
|
||||||
|
var clone = new DirectedGraph<T>();
|
||||||
|
clone.AddVertices(Vertices.ToArray());
|
||||||
|
|
||||||
|
foreach (var v in Vertices)
|
||||||
|
{
|
||||||
|
foreach (var n in Neighbors(v))
|
||||||
|
{
|
||||||
|
clone.AddEdge(v, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectedGraph<T> SubGraph(params T[] subVertices)
|
||||||
|
{
|
||||||
|
var subGraph = new DirectedGraph<T>();
|
||||||
|
subGraph.AddVertices(subVertices.ToArray());
|
||||||
|
|
||||||
|
foreach (var v in Vertices)
|
||||||
|
{
|
||||||
|
if (Vertices.Contains(v))
|
||||||
|
{
|
||||||
|
var neighbors = Neighbors(v);
|
||||||
|
foreach (var u in neighbors)
|
||||||
|
{
|
||||||
|
if (subVertices.Contains(u))
|
||||||
|
{
|
||||||
|
subGraph.AddEdge(v, u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subGraph;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,26 @@ namespace Tests
|
||||||
Assert.That(myGraph.Neighbors(2), Does.Not.Contain(3));
|
Assert.That(myGraph.Neighbors(2), Does.Not.Contain(3));
|
||||||
Assert.That(myGraph.Neighbors(2), Does.Contain(4));
|
Assert.That(myGraph.Neighbors(2), Does.Contain(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void RemoveVertex()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(1, 2, 3, 4);
|
||||||
|
myGraph.AddEdges(
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 3),
|
||||||
|
Tuple.Create(2, 4),
|
||||||
|
Tuple.Create(3, 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph.RemoveVertex(2);
|
||||||
|
|
||||||
|
myGraph.Vertices.Should().NotContain(2);
|
||||||
|
myGraph.Neighbors(1).Should().NotContain(2);
|
||||||
|
myGraph.Neighbors(3).Should().Contain(4);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void NodeDFS()
|
public void NodeDFS()
|
||||||
{
|
{
|
||||||
|
@ -212,5 +232,119 @@ namespace Tests
|
||||||
result.Should().ContainEquivalentOf(sccC);
|
result.Should().ContainEquivalentOf(sccC);
|
||||||
Assert.That(result.Count, Is.EqualTo(3));
|
Assert.That(result.Count, Is.EqualTo(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Clone()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(1, 2, 3, 4);
|
||||||
|
myGraph.AddEdges(
|
||||||
|
Tuple.Create(1, 1),
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 3),
|
||||||
|
Tuple.Create(2, 1),
|
||||||
|
Tuple.Create(3, 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
var clone = myGraph.Clone();
|
||||||
|
Assert.That(clone, Is.Not.EqualTo(myGraph));
|
||||||
|
clone.Vertices.Should().BeEquivalentTo(1, 2, 3, 4);
|
||||||
|
clone.Neighbors(1).Should().BeEquivalentTo(1, 2);
|
||||||
|
clone.Neighbors(2).Should().BeEquivalentTo(3, 1);
|
||||||
|
clone.Neighbors(3).Should().BeEquivalentTo(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SubGraph()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(1, 2, 3, 4);
|
||||||
|
myGraph.AddEdges(
|
||||||
|
Tuple.Create(1, 1),
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 3),
|
||||||
|
Tuple.Create(2, 1),
|
||||||
|
Tuple.Create(3, 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
var subGraph = myGraph.SubGraph(1, 2, 3);
|
||||||
|
subGraph.Vertices.Should().BeEquivalentTo(1, 2, 3);
|
||||||
|
subGraph.Neighbors(1).Should().BeEquivalentTo(1, 2);
|
||||||
|
subGraph.Neighbors(2).Should().BeEquivalentTo(1, 3);
|
||||||
|
subGraph.Neighbors(3).Should().NotContain(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SimpleCyclesSimple()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(0, 1, 2);
|
||||||
|
myGraph.AddEdges(
|
||||||
|
Tuple.Create(0, 0),
|
||||||
|
Tuple.Create(0, 1),
|
||||||
|
Tuple.Create(0, 2),
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 0),
|
||||||
|
Tuple.Create(2, 1),
|
||||||
|
Tuple.Create(2, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = myGraph.SimpleCycles();
|
||||||
|
|
||||||
|
var cycleA = new int[] { 0 };
|
||||||
|
var cycleB = new int[] { 0, 1, 2 };
|
||||||
|
var cycleC = new int[] { 0, 2 };
|
||||||
|
var cycleD = new int[] { 1, 2 };
|
||||||
|
var cycleE = new int[] { 2 };
|
||||||
|
|
||||||
|
result.Should().ContainEquivalentOf(cycleA);
|
||||||
|
result.Should().ContainEquivalentOf(cycleB);
|
||||||
|
result.Should().ContainEquivalentOf(cycleC);
|
||||||
|
result.Should().ContainEquivalentOf(cycleD);
|
||||||
|
result.Should().ContainEquivalentOf(cycleE);
|
||||||
|
result.Should().HaveCount(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SimpleCyclesComplex()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||||
|
myGraph.AddEdges(
|
||||||
|
Tuple.Create(0, 1),
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 3),
|
||||||
|
Tuple.Create(3, 0),
|
||||||
|
Tuple.Create(0, 3),
|
||||||
|
Tuple.Create(3, 4),
|
||||||
|
Tuple.Create(4, 5),
|
||||||
|
Tuple.Create(5, 0),
|
||||||
|
Tuple.Create(0, 1),
|
||||||
|
Tuple.Create(1, 6),
|
||||||
|
Tuple.Create(6, 7),
|
||||||
|
Tuple.Create(7, 8),
|
||||||
|
Tuple.Create(8, 0),
|
||||||
|
Tuple.Create(8, 9)
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = myGraph.SimpleCycles();
|
||||||
|
var cycleA = new int[] { 0, 3 };
|
||||||
|
var cycleB = new int[] { 0, 1, 2, 3, 4, 5 };
|
||||||
|
var cycleC = new int[] { 0, 1, 2, 3 };
|
||||||
|
var cycleD = new int[] { 0, 3, 4, 5 };
|
||||||
|
var cycleE = new int[] { 0, 1, 6, 7, 8 };
|
||||||
|
|
||||||
|
foreach (var cycle in result)
|
||||||
|
{
|
||||||
|
Console.WriteLine(string.Join(", ", cycle));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Should().ContainEquivalentOf(cycleA);
|
||||||
|
result.Should().ContainEquivalentOf(cycleB);
|
||||||
|
result.Should().ContainEquivalentOf(cycleC);
|
||||||
|
result.Should().ContainEquivalentOf(cycleD);
|
||||||
|
result.Should().ContainEquivalentOf(cycleE);
|
||||||
|
result.Should().HaveCount(5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue