Skip to content

Commit 94689d8

Browse files
Adding Kosaraju's Algorithm to find strongly connected components (#745)
* Add time and space complexity information to various algorithms * Added the implementation of Kosaraju algorithm * Revert "Add time and space complexity information to various algorithms" This reverts commit 51915ff. --------- Co-authored-by: Rak Laptudirm <[email protected]>
1 parent e64d1f5 commit 94689d8

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

Diff for: graph/kosaraju.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// kosaraju.go
2+
// description: Implementation of Kosaraju's algorithm to find Strongly Connected Components (SCCs) in a directed graph.
3+
// details: The algorithm consists of three steps:
4+
// 1. Perform DFS and fill the stack with vertices in the order of their finish times.
5+
// 2. Create a transposed graph by reversing all edges.
6+
// 3. Perform DFS on the transposed graph in the order defined by the stack to find SCCs.
7+
// time: O(V + E), where V is the number of vertices and E is the number of edges in the graph.
8+
// space: O(V), where V is the number of vertices in the graph.
9+
// ref link: https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
10+
// author: mapcrafter2048
11+
12+
package graph
13+
14+
// Kosaraju returns a list of Strongly Connected Components (SCCs).
15+
func (g *Graph) Kosaraju() [][]int {
16+
stack := []int{}
17+
visited := make([]bool, g.vertices)
18+
19+
// Step 1: Perform DFS and fill stack based on finish times.
20+
for i := 0; i < g.vertices; i++ {
21+
if !visited[i] {
22+
g.fillOrder(i, visited, &stack)
23+
}
24+
}
25+
26+
// Step 2: Create a transposed graph.
27+
transposed := g.transpose()
28+
29+
// Step 3: Perform DFS on the transposed graph in the order defined by the stack.
30+
visited = make([]bool, g.vertices)
31+
var sccs [][]int
32+
33+
for len(stack) > 0 {
34+
// Pop vertex from stack
35+
v := stack[len(stack)-1]
36+
stack = stack[:len(stack)-1]
37+
38+
// Perform DFS if not already visited.
39+
if !visited[v] {
40+
scc := []int{}
41+
transposed.dfs(v, visited, &scc)
42+
sccs = append(sccs, scc)
43+
}
44+
}
45+
46+
return sccs
47+
}
48+
49+
// Helper function to fill the stack with vertices in the order of their finish times.
50+
func (g *Graph) fillOrder(v int, visited []bool, stack *[]int) {
51+
visited[v] = true
52+
53+
for neighbor := range g.edges[v] {
54+
if !visited[neighbor] {
55+
g.fillOrder(neighbor, visited, stack)
56+
}
57+
}
58+
59+
// Push the current vertex to the stack after exploring all neighbors.
60+
*stack = append(*stack, v)
61+
}
62+
63+
// Helper function to create a transposed (reversed) graph.
64+
func (g *Graph) transpose() *Graph {
65+
transposed := &Graph{
66+
vertices: g.vertices,
67+
edges: make(map[int]map[int]int),
68+
}
69+
70+
for v, neighbors := range g.edges {
71+
for neighbor := range neighbors {
72+
if transposed.edges[neighbor] == nil {
73+
transposed.edges[neighbor] = make(map[int]int)
74+
}
75+
transposed.edges[neighbor][v] = 1 // Add the reversed edge
76+
}
77+
}
78+
79+
return transposed
80+
}
81+
82+
// Helper DFS function used in the transposed graph to collect SCCs.
83+
func (g *Graph) dfs(v int, visited []bool, scc *[]int) {
84+
visited[v] = true
85+
*scc = append(*scc, v)
86+
87+
for neighbor := range g.edges[v] {
88+
if !visited[neighbor] {
89+
g.dfs(neighbor, visited, scc)
90+
}
91+
}
92+
}

Diff for: graph/kosaraju_test.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package graph
2+
3+
import (
4+
"reflect"
5+
"sort"
6+
"testing"
7+
)
8+
9+
func TestKosaraju(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
vertices int
13+
edges map[int][]int
14+
expected [][]int
15+
}{
16+
{
17+
name: "Single SCC",
18+
vertices: 5,
19+
edges: map[int][]int{
20+
0: {1},
21+
1: {2},
22+
2: {0, 3},
23+
3: {4},
24+
4: {},
25+
},
26+
expected: [][]int{{4}, {3}, {0, 2, 1}},
27+
},
28+
{
29+
name: "Multiple SCCs",
30+
vertices: 8,
31+
edges: map[int][]int{
32+
0: {1},
33+
1: {2},
34+
2: {0, 3},
35+
3: {4},
36+
4: {5},
37+
5: {3, 6},
38+
6: {7},
39+
7: {6},
40+
},
41+
expected: [][]int{{6, 7}, {3, 4, 5}, {0, 2, 1}},
42+
},
43+
{
44+
name: "Disconnected graph",
45+
vertices: 4,
46+
edges: map[int][]int{
47+
0: {1},
48+
1: {},
49+
2: {3},
50+
3: {},
51+
},
52+
expected: [][]int{{1}, {0}, {3}, {2}},
53+
},
54+
{
55+
name: "No edges",
56+
vertices: 3,
57+
edges: map[int][]int{
58+
0: {},
59+
1: {},
60+
2: {},
61+
},
62+
expected: [][]int{{0}, {1}, {2}},
63+
},
64+
}
65+
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
// Initializing graph
69+
graph := &Graph{
70+
vertices: tt.vertices,
71+
edges: make(map[int]map[int]int),
72+
}
73+
for v, neighbors := range tt.edges {
74+
graph.edges[v] = make(map[int]int)
75+
for _, neighbor := range neighbors {
76+
graph.edges[v][neighbor] = 1
77+
}
78+
}
79+
80+
// Running Kosaraju's algorithm to get the SCCs
81+
result := graph.Kosaraju()
82+
83+
// Sort the expected and result SCCs to ensure order doesn't matter
84+
sortSlices(tt.expected)
85+
sortSlices(result)
86+
87+
// Compare the sorted SCCs
88+
if !reflect.DeepEqual(result, tt.expected) {
89+
t.Errorf("expected %v, got %v", tt.expected, result)
90+
}
91+
})
92+
}
93+
}
94+
95+
// Utility function to sort the slices and their contents
96+
func sortSlices(s [][]int) {
97+
for _, inner := range s {
98+
sort.Ints(inner)
99+
}
100+
sort.Slice(s, func(i, j int) bool {
101+
if len(s[i]) == 0 || len(s[j]) == 0 {
102+
return len(s[i]) < len(s[j])
103+
}
104+
return s[i][0] < s[j][0]
105+
})
106+
}

0 commit comments

Comments
 (0)