-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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]>
- Loading branch information
1 parent
e64d1f5
commit 94689d8
Showing
2 changed files
with
198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// kosaraju.go | ||
// description: Implementation of Kosaraju's algorithm to find Strongly Connected Components (SCCs) in a directed graph. | ||
// details: The algorithm consists of three steps: | ||
// 1. Perform DFS and fill the stack with vertices in the order of their finish times. | ||
// 2. Create a transposed graph by reversing all edges. | ||
// 3. Perform DFS on the transposed graph in the order defined by the stack to find SCCs. | ||
// time: O(V + E), where V is the number of vertices and E is the number of edges in the graph. | ||
// space: O(V), where V is the number of vertices in the graph. | ||
// ref link: https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm | ||
// author: mapcrafter2048 | ||
|
||
package graph | ||
|
||
// Kosaraju returns a list of Strongly Connected Components (SCCs). | ||
func (g *Graph) Kosaraju() [][]int { | ||
stack := []int{} | ||
visited := make([]bool, g.vertices) | ||
|
||
// Step 1: Perform DFS and fill stack based on finish times. | ||
for i := 0; i < g.vertices; i++ { | ||
if !visited[i] { | ||
g.fillOrder(i, visited, &stack) | ||
} | ||
} | ||
|
||
// Step 2: Create a transposed graph. | ||
transposed := g.transpose() | ||
|
||
// Step 3: Perform DFS on the transposed graph in the order defined by the stack. | ||
visited = make([]bool, g.vertices) | ||
var sccs [][]int | ||
|
||
for len(stack) > 0 { | ||
// Pop vertex from stack | ||
v := stack[len(stack)-1] | ||
stack = stack[:len(stack)-1] | ||
|
||
// Perform DFS if not already visited. | ||
if !visited[v] { | ||
scc := []int{} | ||
transposed.dfs(v, visited, &scc) | ||
sccs = append(sccs, scc) | ||
} | ||
} | ||
|
||
return sccs | ||
} | ||
|
||
// Helper function to fill the stack with vertices in the order of their finish times. | ||
func (g *Graph) fillOrder(v int, visited []bool, stack *[]int) { | ||
visited[v] = true | ||
|
||
for neighbor := range g.edges[v] { | ||
if !visited[neighbor] { | ||
g.fillOrder(neighbor, visited, stack) | ||
} | ||
} | ||
|
||
// Push the current vertex to the stack after exploring all neighbors. | ||
*stack = append(*stack, v) | ||
} | ||
|
||
// Helper function to create a transposed (reversed) graph. | ||
func (g *Graph) transpose() *Graph { | ||
transposed := &Graph{ | ||
vertices: g.vertices, | ||
edges: make(map[int]map[int]int), | ||
} | ||
|
||
for v, neighbors := range g.edges { | ||
for neighbor := range neighbors { | ||
if transposed.edges[neighbor] == nil { | ||
transposed.edges[neighbor] = make(map[int]int) | ||
} | ||
transposed.edges[neighbor][v] = 1 // Add the reversed edge | ||
} | ||
} | ||
|
||
return transposed | ||
} | ||
|
||
// Helper DFS function used in the transposed graph to collect SCCs. | ||
func (g *Graph) dfs(v int, visited []bool, scc *[]int) { | ||
visited[v] = true | ||
*scc = append(*scc, v) | ||
|
||
for neighbor := range g.edges[v] { | ||
if !visited[neighbor] { | ||
g.dfs(neighbor, visited, scc) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package graph | ||
|
||
import ( | ||
"reflect" | ||
"sort" | ||
"testing" | ||
) | ||
|
||
func TestKosaraju(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
vertices int | ||
edges map[int][]int | ||
expected [][]int | ||
}{ | ||
{ | ||
name: "Single SCC", | ||
vertices: 5, | ||
edges: map[int][]int{ | ||
0: {1}, | ||
1: {2}, | ||
2: {0, 3}, | ||
3: {4}, | ||
4: {}, | ||
}, | ||
expected: [][]int{{4}, {3}, {0, 2, 1}}, | ||
}, | ||
{ | ||
name: "Multiple SCCs", | ||
vertices: 8, | ||
edges: map[int][]int{ | ||
0: {1}, | ||
1: {2}, | ||
2: {0, 3}, | ||
3: {4}, | ||
4: {5}, | ||
5: {3, 6}, | ||
6: {7}, | ||
7: {6}, | ||
}, | ||
expected: [][]int{{6, 7}, {3, 4, 5}, {0, 2, 1}}, | ||
}, | ||
{ | ||
name: "Disconnected graph", | ||
vertices: 4, | ||
edges: map[int][]int{ | ||
0: {1}, | ||
1: {}, | ||
2: {3}, | ||
3: {}, | ||
}, | ||
expected: [][]int{{1}, {0}, {3}, {2}}, | ||
}, | ||
{ | ||
name: "No edges", | ||
vertices: 3, | ||
edges: map[int][]int{ | ||
0: {}, | ||
1: {}, | ||
2: {}, | ||
}, | ||
expected: [][]int{{0}, {1}, {2}}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Initializing graph | ||
graph := &Graph{ | ||
vertices: tt.vertices, | ||
edges: make(map[int]map[int]int), | ||
} | ||
for v, neighbors := range tt.edges { | ||
graph.edges[v] = make(map[int]int) | ||
for _, neighbor := range neighbors { | ||
graph.edges[v][neighbor] = 1 | ||
} | ||
} | ||
|
||
// Running Kosaraju's algorithm to get the SCCs | ||
result := graph.Kosaraju() | ||
|
||
// Sort the expected and result SCCs to ensure order doesn't matter | ||
sortSlices(tt.expected) | ||
sortSlices(result) | ||
|
||
// Compare the sorted SCCs | ||
if !reflect.DeepEqual(result, tt.expected) { | ||
t.Errorf("expected %v, got %v", tt.expected, result) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// Utility function to sort the slices and their contents | ||
func sortSlices(s [][]int) { | ||
for _, inner := range s { | ||
sort.Ints(inner) | ||
} | ||
sort.Slice(s, func(i, j int) bool { | ||
if len(s[i]) == 0 || len(s[j]) == 0 { | ||
return len(s[i]) < len(s[j]) | ||
} | ||
return s[i][0] < s[j][0] | ||
}) | ||
} |