Skip to content

Commit

Permalink
Adding Kosaraju's Algorithm to find strongly connected components (#745)
Browse files Browse the repository at this point in the history
* 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
mapcrafter2048 and raklaptudirm authored Oct 31, 2024
1 parent e64d1f5 commit 94689d8
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 0 deletions.
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]
})
}

0 comments on commit 94689d8

Please sign in to comment.