diff --git a/graph/kosaraju.go b/graph/kosaraju.go new file mode 100644 index 000000000..d36949e5f --- /dev/null +++ b/graph/kosaraju.go @@ -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) + } + } +} diff --git a/graph/kosaraju_test.go b/graph/kosaraju_test.go new file mode 100644 index 000000000..360b72a3e --- /dev/null +++ b/graph/kosaraju_test.go @@ -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] + }) +}