Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Kosaraju's Algorithm to find strongly connected components #745

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions graph/kosaraju.go
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)
}
}
}
106 changes: 106 additions & 0 deletions graph/kosaraju_test.go
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]
})
}
Loading