Skip to content

Commit 61e5f1c

Browse files
committed
Add Tarjan's strongly connected components algorithm
1 parent 411763c commit 61e5f1c

File tree

2 files changed

+345
-0
lines changed

2 files changed

+345
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
using Algorithms.Graph;
2+
using NUnit.Framework;
3+
using FluentAssertions;
4+
using System.Linq;
5+
6+
namespace Algorithms.Tests.Graph;
7+
8+
public class TarjanStronglyConnectedComponentsTests
9+
{
10+
[Test]
11+
public void FindSCCs_SimpleGraph_ReturnsCorrectSCCs()
12+
{
13+
var tarjan = new TarjanStronglyConnectedComponents(3);
14+
tarjan.AddEdge(0, 1);
15+
tarjan.AddEdge(1, 2);
16+
tarjan.AddEdge(2, 0);
17+
18+
var sccs = tarjan.FindSCCs();
19+
20+
sccs.Should().HaveCount(1);
21+
sccs[0].Should().BeEquivalentTo(new[] { 0, 1, 2 });
22+
}
23+
24+
[Test]
25+
public void FindSCCs_TwoSeparateSCCs_ReturnsBothSCCs()
26+
{
27+
var tarjan = new TarjanStronglyConnectedComponents(4);
28+
tarjan.AddEdge(0, 1);
29+
tarjan.AddEdge(1, 0);
30+
tarjan.AddEdge(2, 3);
31+
tarjan.AddEdge(3, 2);
32+
33+
var sccs = tarjan.FindSCCs();
34+
35+
sccs.Should().HaveCount(2);
36+
}
37+
38+
[Test]
39+
public void FindSCCs_DisconnectedVertices_EachVertexIsSCC()
40+
{
41+
var tarjan = new TarjanStronglyConnectedComponents(3);
42+
43+
var sccs = tarjan.FindSCCs();
44+
45+
sccs.Should().HaveCount(3);
46+
sccs.Should().OnlyContain(scc => scc.Count == 1);
47+
}
48+
49+
[Test]
50+
public void FindSCCs_ComplexGraph_ReturnsCorrectSCCs()
51+
{
52+
var tarjan = new TarjanStronglyConnectedComponents(8);
53+
tarjan.AddEdge(0, 1);
54+
tarjan.AddEdge(1, 2);
55+
tarjan.AddEdge(2, 0);
56+
tarjan.AddEdge(2, 3);
57+
tarjan.AddEdge(3, 4);
58+
tarjan.AddEdge(4, 5);
59+
tarjan.AddEdge(5, 3);
60+
tarjan.AddEdge(5, 6);
61+
tarjan.AddEdge(6, 7);
62+
tarjan.AddEdge(7, 6);
63+
64+
var sccs = tarjan.FindSCCs();
65+
66+
sccs.Should().HaveCount(3);
67+
}
68+
69+
[Test]
70+
public void GetSCCCount_AfterFindingSCCs_ReturnsCorrectCount()
71+
{
72+
var tarjan = new TarjanStronglyConnectedComponents(5);
73+
tarjan.AddEdge(0, 1);
74+
tarjan.AddEdge(1, 0);
75+
tarjan.AddEdge(2, 3);
76+
tarjan.AddEdge(3, 4);
77+
tarjan.AddEdge(4, 2);
78+
79+
tarjan.FindSCCs();
80+
var count = tarjan.GetSCCCount();
81+
82+
count.Should().Be(2);
83+
}
84+
85+
[Test]
86+
public void InSameSCC_VerticesInSameSCC_ReturnsTrue()
87+
{
88+
var tarjan = new TarjanStronglyConnectedComponents(3);
89+
tarjan.AddEdge(0, 1);
90+
tarjan.AddEdge(1, 2);
91+
tarjan.AddEdge(2, 0);
92+
93+
var result = tarjan.InSameSCC(0, 2);
94+
95+
result.Should().BeTrue();
96+
}
97+
98+
[Test]
99+
public void InSameSCC_VerticesInDifferentSCCs_ReturnsFalse()
100+
{
101+
var tarjan = new TarjanStronglyConnectedComponents(4);
102+
tarjan.AddEdge(0, 1);
103+
tarjan.AddEdge(1, 0);
104+
tarjan.AddEdge(2, 3);
105+
106+
var result = tarjan.InSameSCC(0, 2);
107+
108+
result.Should().BeFalse();
109+
}
110+
111+
[Test]
112+
public void GetSCC_ValidVertex_ReturnsSCCContainingVertex()
113+
{
114+
var tarjan = new TarjanStronglyConnectedComponents(3);
115+
tarjan.AddEdge(0, 1);
116+
tarjan.AddEdge(1, 2);
117+
tarjan.AddEdge(2, 0);
118+
119+
var scc = tarjan.GetSCC(1);
120+
121+
scc.Should().NotBeNull();
122+
scc.Should().Contain(1);
123+
scc.Should().HaveCount(3);
124+
}
125+
126+
[Test]
127+
public void BuildCondensationGraph_ComplexGraph_ReturnsDAG()
128+
{
129+
var tarjan = new TarjanStronglyConnectedComponents(6);
130+
tarjan.AddEdge(0, 1);
131+
tarjan.AddEdge(1, 0);
132+
tarjan.AddEdge(1, 2);
133+
tarjan.AddEdge(2, 3);
134+
tarjan.AddEdge(3, 4);
135+
tarjan.AddEdge(4, 5);
136+
tarjan.AddEdge(5, 3);
137+
138+
var condensation = tarjan.BuildCondensationGraph();
139+
140+
condensation.Should().NotBeNull();
141+
condensation.Length.Should().Be(2);
142+
}
143+
144+
[Test]
145+
public void AddEdge_InvalidVertex_ThrowsException()
146+
{
147+
var tarjan = new TarjanStronglyConnectedComponents(3);
148+
149+
var act = () => tarjan.AddEdge(0, 5);
150+
151+
act.Should().Throw<ArgumentOutOfRangeException>();
152+
}
153+
154+
[Test]
155+
public void FindSCCs_SingleVertex_ReturnsSingleSCC()
156+
{
157+
var tarjan = new TarjanStronglyConnectedComponents(1);
158+
159+
var sccs = tarjan.FindSCCs();
160+
161+
sccs.Should().HaveCount(1);
162+
sccs[0].Should().BeEquivalentTo(new[] { 0 });
163+
}
164+
165+
[Test]
166+
public void FindSCCs_LinearChain_EachVertexIsSCC()
167+
{
168+
var tarjan = new TarjanStronglyConnectedComponents(4);
169+
tarjan.AddEdge(0, 1);
170+
tarjan.AddEdge(1, 2);
171+
tarjan.AddEdge(2, 3);
172+
173+
var sccs = tarjan.FindSCCs();
174+
175+
sccs.Should().HaveCount(4);
176+
}
177+
178+
[Test]
179+
public void FindSCCs_SelfLoop_VertexIsSCC()
180+
{
181+
var tarjan = new TarjanStronglyConnectedComponents(2);
182+
tarjan.AddEdge(0, 0);
183+
tarjan.AddEdge(0, 1);
184+
185+
var sccs = tarjan.FindSCCs();
186+
187+
sccs.Should().Contain(scc => scc.Contains(0) && scc.Count == 1);
188+
}
189+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Algorithms.Graph;
6+
7+
/// <summary>
8+
/// Tarjan's algorithm for finding strongly connected components in a directed graph.
9+
/// Uses depth-first search with a stack to identify SCCs in O(V + E) time.
10+
/// </summary>
11+
public class TarjanStronglyConnectedComponents
12+
{
13+
private readonly List<int>[] graph;
14+
private readonly int[] ids;
15+
private readonly int[] low;
16+
private readonly bool[] onStack;
17+
private readonly Stack<int> stack;
18+
private readonly List<List<int>> sccs;
19+
private int id;
20+
21+
public TarjanStronglyConnectedComponents(int vertices)
22+
{
23+
graph = new List<int>[vertices];
24+
ids = new int[vertices];
25+
low = new int[vertices];
26+
onStack = new bool[vertices];
27+
stack = new Stack<int>();
28+
sccs = new List<List<int>>();
29+
30+
for (int i = 0; i < vertices; i++)
31+
{
32+
graph[i] = new List<int>();
33+
ids[i] = -1;
34+
}
35+
}
36+
37+
/// <summary>
38+
/// Adds a directed edge from u to v.
39+
/// </summary>
40+
public void AddEdge(int u, int v)
41+
{
42+
if (u < 0 || u >= graph.Length || v < 0 || v >= graph.Length)
43+
throw new ArgumentOutOfRangeException();
44+
45+
graph[u].Add(v);
46+
}
47+
48+
/// <summary>
49+
/// Finds all strongly connected components.
50+
/// </summary>
51+
/// <returns>List of SCCs, where each SCC is a list of vertex indices.</returns>
52+
public List<List<int>> FindSCCs()
53+
{
54+
for (int i = 0; i < graph.Length; i++)
55+
{
56+
if (ids[i] == -1)
57+
Dfs(i);
58+
}
59+
60+
return sccs;
61+
}
62+
63+
/// <summary>
64+
/// Gets the number of strongly connected components.
65+
/// </summary>
66+
public int GetSCCCount() => sccs.Count;
67+
68+
/// <summary>
69+
/// Checks if two vertices are in the same SCC.
70+
/// </summary>
71+
public bool InSameSCC(int u, int v)
72+
{
73+
if (sccs.Count == 0) FindSCCs();
74+
75+
foreach (var scc in sccs)
76+
{
77+
if (scc.Contains(u) && scc.Contains(v))
78+
return true;
79+
}
80+
81+
return false;
82+
}
83+
84+
/// <summary>
85+
/// Gets the SCC containing the given vertex.
86+
/// </summary>
87+
public List<int>? GetSCC(int vertex)
88+
{
89+
if (sccs.Count == 0) FindSCCs();
90+
91+
return sccs.FirstOrDefault(scc => scc.Contains(vertex));
92+
}
93+
94+
/// <summary>
95+
/// Builds the condensation graph (DAG of SCCs).
96+
/// </summary>
97+
public List<int>[] BuildCondensationGraph()
98+
{
99+
if (sccs.Count == 0) FindSCCs();
100+
101+
var sccIndex = new int[graph.Length];
102+
for (int i = 0; i < sccs.Count; i++)
103+
{
104+
foreach (var vertex in sccs[i])
105+
sccIndex[vertex] = i;
106+
}
107+
108+
var condensation = new List<int>[sccs.Count];
109+
for (int i = 0; i < sccs.Count; i++)
110+
condensation[i] = new List<int>();
111+
112+
var edges = new HashSet<(int, int)>();
113+
for (int u = 0; u < graph.Length; u++)
114+
{
115+
foreach (var v in graph[u])
116+
{
117+
int sccU = sccIndex[u];
118+
int sccV = sccIndex[v];
119+
120+
if (sccU != sccV && !edges.Contains((sccU, sccV)))
121+
{
122+
condensation[sccU].Add(sccV);
123+
edges.Add((sccU, sccV));
124+
}
125+
}
126+
}
127+
128+
return condensation;
129+
}
130+
131+
private void Dfs(int at)
132+
{
133+
stack.Push(at);
134+
onStack[at] = true;
135+
ids[at] = low[at] = id++;
136+
137+
foreach (var to in graph[at])
138+
{
139+
if (ids[to] == -1) Dfs(to);
140+
if (onStack[to]) low[at] = Math.Min(low[at], low[to]);
141+
}
142+
143+
if (ids[at] == low[at])
144+
{
145+
var scc = new List<int>();
146+
while (true)
147+
{
148+
int node = stack.Pop();
149+
onStack[node] = false;
150+
scc.Add(node);
151+
if (node == at) break;
152+
}
153+
sccs.Add(scc);
154+
}
155+
}
156+
}

0 commit comments

Comments
 (0)