问题
I have the following (recursive) implementation of Tarjan's algorithm to find strongly connected components in a graph and it works fine:
public class StronglyConnectedComponents
{
public static List<List<int>> Search(Graph graph)
{
StronglyConnectedComponents scc = new StronglyConnectedComponents();
return scc.Tarjan(graph);
}
private int preCount;
private int[] low;
private bool[] visited;
private Graph graph;
private List<List<int>> stronglyConnectedComponents = new List<List<int>>();
private Stack<int> stack = new Stack<int>();
public List<List<int>> Tarjan(Graph graph)
{
this.graph = graph;
low = new int[graph.VertexCount];
visited = new bool[graph.VertexCount];
for (int v = 0; v < graph.VertexCount; v++) if (!visited[v]) DFS(v);
return stronglyConnectedComponents;
}
public void DFS(int v)
{
low[v] = preCount++;
visited[v] = true;
stack.Push(v);
int min = low[v];
int edgeCount = graph.OutgoingEdgeCount(v);
for (int i = 0; i < edgeCount; i++)
{
var edge = graph.OutgoingEdge(v, i);
int target = edge.Target;
if (!visited[target]) DFS(target);
if (low[target] < min) min = low[target];
}
if (min < low[v])
{
low[v] = min;
return;
}
List<int> component = new List<int>();
int w;
do
{
w = stack.Pop();
component.Add(w);
low[w] = graph.VertexCount;
} while (w != v);
stronglyConnectedComponents.Add(component);
}
}
But on large graphs, obviously, the recursive version will throw a StackOverflowException. Therefore I want to make the algorithm non-recursive.
I tried to replace the function DFS
with the following (non-recursive) one, but the algorithm doesn't work anymore. Can anybody help?
private void DFS2(int vertex)
{
bool[] visited = new bool[graph.VertexCount];
Stack<int> stack = new Stack<int>();
stack.Push(vertex);
int min = low[vertex];
while (stack.Count > 0)
{
int v = stack.Pop();
if (visited[v]) continue;
visited[v] = true;
int edgeCount = graph.OutgoingEdgeCount(v);
for (int i = 0; i < edgeCount; i++)
{
int target = graph.OutgoingEdge(v, i).Target;
stack.Push(target);
if (low[target] < min) min = low[target];
}
}
if (min < low[vertex])
{
low[vertex] = min;
return;
}
List<int> component = new List<int>();
int w;
do
{
w = stack.Pop();
component.Add(w);
low[w] = graph.VertexCount;
} while (w != vertex);
stronglyConnectedComponents.Add(component);
}
The following code shows the test:
public void CanFindStronglyConnectedComponents()
{
Graph graph = new Graph(8);
graph.AddEdge(0, 1);
graph.AddEdge(1, 2);
graph.AddEdge(2, 3);
graph.AddEdge(3, 2);
graph.AddEdge(3, 7);
graph.AddEdge(7, 3);
graph.AddEdge(2, 6);
graph.AddEdge(7, 6);
graph.AddEdge(5, 6);
graph.AddEdge(6, 5);
graph.AddEdge(1, 5);
graph.AddEdge(4, 5);
graph.AddEdge(4, 0);
graph.AddEdge(1, 4);
var scc = StronglyConnectedComponents.Search(graph);
Assert.AreEqual(3, scc.Count);
Assert.IsTrue(SetsEqual(Set(5, 6), scc[0]));
Assert.IsTrue(SetsEqual(Set(7, 3, 2), scc[1]));
Assert.IsTrue(SetsEqual(Set(4, 1, 0), scc[2]));
}
private IEnumerable<int> Set(params int[] set) => set;
private bool SetsEqual(IEnumerable<int> set1, IEnumerable<int> set2)
{
if (set1.Count() != set2.Count()) return false;
return set1.Intersect(set2).Count() == set1.Count();
}
回答1:
Here is a direct non recursive translation of the original recursive implementation (assuming it's correct):
public static List<List<int>> Search(Graph graph)
{
var stronglyConnectedComponents = new List<List<int>>();
int preCount = 0;
var low = new int[graph.VertexCount];
var visited = new bool[graph.VertexCount];
var stack = new Stack<int>();
var minStack = new Stack<int>();
var enumeratorStack = new Stack<IEnumerator<int>>();
var enumerator = Enumerable.Range(0, graph.VertexCount).GetEnumerator();
while (true)
{
if (enumerator.MoveNext())
{
int v = enumerator.Current;
if (!visited[v])
{
low[v] = preCount++;
visited[v] = true;
stack.Push(v);
int min = low[v];
// Level down
minStack.Push(min);
enumeratorStack.Push(enumerator);
enumerator = Enumerable.Range(0, graph.OutgoingEdgeCount(v))
.Select(i => graph.OutgoingEdge(v, i).Target)
.GetEnumerator();
}
else if (minStack.Count > 0)
{
int min = minStack.Pop();
if (low[v] < min) min = low[v];
minStack.Push(min);
}
}
else
{
// Level up
if (enumeratorStack.Count == 0) break;
enumerator = enumeratorStack.Pop();
int v = enumerator.Current;
int min = minStack.Pop();
if (min < low[v])
{
low[v] = min;
}
else
{
List<int> component = new List<int>();
int w;
do
{
w = stack.Pop();
component.Add(w);
low[w] = graph.VertexCount;
} while (w != v);
stronglyConnectedComponents.Add(component);
}
if (minStack.Count > 0)
{
min = minStack.Pop();
if (low[v] < min) min = low[v];
minStack.Push(min);
}
}
}
return stronglyConnectedComponents;
}
As usual for such direct translations, you need an explicit stack used to store the state that needs to be restored after "returning" from the recursive call. In this case, it's the level vertex enumerator and min
variable.
Note that the existing stack
variable cannot be used because while the processing vertex is pushed there, it's not always popped on exit (the return
line in the recursive implementation), which is a specific requirement for this algorithm.
来源:https://stackoverflow.com/questions/46511682/non-recursive-version-of-tarjans-algorithm