Skip to content

Commit c65014e

Browse files
Add Kosaraju's Strongly Connected Components algorithm
Features: - Complete implementation of Kosaraju's algorithm for finding SCCs in directed graphs - Time complexity: O(V + E) with two-pass DFS approach - Space complexity: O(V) for visited arrays and recursion stack Implementation includes: - Main kosaraju_scc() function with comprehensive error handling - Helper functions for DFS traversals and transpose graph creation - Detailed algorithm steps with console output for educational purposes - Multiple example graphs demonstrating different SCC scenarios - Text-based graph visualization utilities - Utility function for converting named graphs to numeric format Algorithm Steps: 1. Perform DFS on original graph to determine finishing order 2. Create transpose graph (reverse all edge directions) 3. Perform DFS on transpose graph in decreasing finish time order 4. Each DFS tree in step 3 represents one strongly connected component Examples provided: - Simple graphs with multiple SCCs - Linear chains (no cycles) - Complex graphs with bridge connections - Single large SCC (complete cycle) - Disconnected graph components - Social network analysis application Educational value: - Clear step-by-step algorithm explanation - Real-world applications (social networks, web crawling, circuit design) - Comprehensive test cases with expected outputs - Proper R coding style following repository conventions Update DIRECTORY.md: - Add Kosaraju SCC algorithm entry - Add missing Johnson Shortest Paths algorithm entry (alphabetical order)
1 parent 60af194 commit c65014e

File tree

2 files changed

+380
-0
lines changed

2 files changed

+380
-0
lines changed

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
* [Depth First Search](https://github.com/TheAlgorithms/R/blob/HEAD/graph_algorithms/depth_first_search.r)
5858
* [Dijkstra Shortest Path](https://github.com/TheAlgorithms/R/blob/HEAD/graph_algorithms/dijkstra_shortest_path.r)
5959
* [Floyd Warshall](https://github.com/TheAlgorithms/R/blob/HEAD/graph_algorithms/floyd_warshall.r)
60+
* [Johnson Shortest Paths](https://github.com/TheAlgorithms/R/blob/HEAD/graph_algorithms/johnson_shortest_paths.r)
61+
* [Kosaraju Scc](https://github.com/TheAlgorithms/R/blob/HEAD/graph_algorithms/kosaraju_scc.r)
6062

6163
## Machine Learning
6264
* [Gradient Boosting](https://github.com/TheAlgorithms/R/blob/HEAD/machine_learning/gradient_boosting.r)

graph_algorithms/kosaraju_scc.r

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
# Kosaraju's Algorithm for Finding Strongly Connected Components
2+
#
3+
# Kosaraju's algorithm is used to find all strongly connected components (SCCs) in a directed graph.
4+
# A strongly connected component is a maximal set of vertices such that there is a path from
5+
# each vertex to every other vertex in the component.
6+
#
7+
# Algorithm Steps:
8+
# 1. Perform DFS on the original graph and store vertices in finishing order (stack)
9+
# 2. Create transpose graph (reverse all edge directions)
10+
# 3. Perform DFS on transpose graph in the order of decreasing finish times
11+
#
12+
# Time Complexity: O(V + E) where V is vertices and E is edges
13+
# Space Complexity: O(V) for visited arrays and recursion stack
14+
#
15+
# Author: Contributor for TheAlgorithms/R
16+
# Applications: Social network analysis, web crawling, circuit design verification
17+
18+
# Helper function: DFS to fill stack with finishing times
19+
dfs_fill_order <- function(graph, vertex, visited, stack) {
20+
# Mark current vertex as visited
21+
visited[vertex] <- TRUE
22+
23+
# Visit all adjacent vertices
24+
if (as.character(vertex) %in% names(graph)) {
25+
for (neighbor in graph[[as.character(vertex)]]) {
26+
if (!visited[neighbor]) {
27+
result <- dfs_fill_order(graph, neighbor, visited, stack)
28+
stack <- result$stack
29+
visited <- result$visited
30+
}
31+
}
32+
}
33+
34+
# Push current vertex to stack (finishing time)
35+
stack <- c(stack, vertex)
36+
37+
return(list(visited = visited, stack = stack))
38+
}
39+
40+
# Helper function: DFS to collect vertices in current SCC
41+
dfs_collect_scc <- function(transpose_graph, vertex, visited, current_scc) {
42+
# Mark current vertex as visited
43+
visited[vertex] <- TRUE
44+
current_scc <- c(current_scc, vertex)
45+
46+
# Visit all adjacent vertices in transpose graph
47+
if (as.character(vertex) %in% names(transpose_graph)) {
48+
for (neighbor in transpose_graph[[as.character(vertex)]]) {
49+
if (!visited[neighbor]) {
50+
result <- dfs_collect_scc(transpose_graph, neighbor, visited, current_scc)
51+
visited <- result$visited
52+
current_scc <- result$current_scc
53+
}
54+
}
55+
}
56+
57+
return(list(visited = visited, current_scc = current_scc))
58+
}
59+
60+
# Function to create transpose graph (reverse all edges)
61+
create_transpose_graph <- function(graph) {
62+
# Initialize empty transpose graph
63+
transpose_graph <- list()
64+
65+
# Get all vertices
66+
all_vertices <- unique(c(names(graph), unlist(graph)))
67+
68+
# Initialize empty adjacency lists for all vertices
69+
for (vertex in all_vertices) {
70+
transpose_graph[[as.character(vertex)]] <- c()
71+
}
72+
73+
# Reverse all edges
74+
for (vertex in names(graph)) {
75+
for (neighbor in graph[[vertex]]) {
76+
# Add edge from neighbor to vertex (reverse direction)
77+
transpose_graph[[as.character(neighbor)]] <-
78+
c(transpose_graph[[as.character(neighbor)]], as.numeric(vertex))
79+
}
80+
}
81+
82+
# Remove empty adjacency lists
83+
transpose_graph <- transpose_graph[lengths(transpose_graph) > 0 | names(transpose_graph) %in% names(graph)]
84+
85+
return(transpose_graph)
86+
}
87+
88+
# Main Kosaraju's Algorithm function
89+
kosaraju_scc <- function(graph) {
90+
#' Kosaraju's Algorithm for Strongly Connected Components
91+
#'
92+
#' @param graph A named list representing adjacency list of directed graph
93+
#' Format: list("1" = c(2, 3), "2" = c(3), "3" = c())
94+
#' Keys are vertex names (as strings), values are vectors of adjacent vertices
95+
#'
96+
#' @return A list containing:
97+
#' - scc_list: List of strongly connected components (each is a vector of vertices)
98+
#' - scc_count: Number of strongly connected components
99+
#' - vertex_to_scc: Named vector mapping each vertex to its SCC number
100+
#' - transpose_graph: The transpose graph used in algorithm
101+
102+
# Input validation
103+
if (!is.list(graph)) {
104+
stop("Graph must be a list representing adjacency list")
105+
}
106+
107+
if (length(graph) == 0) {
108+
return(list(scc_list = list(), scc_count = 0, vertex_to_scc = c(), transpose_graph = list()))
109+
}
110+
111+
# Get all vertices in the graph
112+
all_vertices <- unique(c(names(graph), unlist(graph)))
113+
max_vertex <- max(all_vertices)
114+
115+
# Initialize visited array for first DFS
116+
visited <- rep(FALSE, max_vertex)
117+
names(visited) <- 1:max_vertex
118+
stack <- c()
119+
120+
# Step 1: Fill vertices in stack according to their finishing times
121+
cat("Step 1: Performing DFS to determine finishing order...\n")
122+
for (vertex in all_vertices) {
123+
if (!visited[vertex]) {
124+
result <- dfs_fill_order(graph, vertex, visited, stack)
125+
visited <- result$visited
126+
stack <- result$stack
127+
}
128+
}
129+
130+
cat("Finishing order (stack):", rev(stack), "\n")
131+
132+
# Step 2: Create transpose graph
133+
cat("Step 2: Creating transpose graph...\n")
134+
transpose_graph <- create_transpose_graph(graph)
135+
136+
# Step 3: Perform DFS on transpose graph in order of decreasing finish times
137+
cat("Step 3: Finding SCCs in transpose graph...\n")
138+
visited <- rep(FALSE, max_vertex)
139+
names(visited) <- 1:max_vertex
140+
141+
scc_list <- list()
142+
scc_count <- 0
143+
vertex_to_scc <- rep(NA, max_vertex)
144+
names(vertex_to_scc) <- 1:max_vertex
145+
146+
# Process vertices in reverse finishing order
147+
for (vertex in rev(stack)) {
148+
if (!visited[vertex]) {
149+
scc_count <- scc_count + 1
150+
result <- dfs_collect_scc(transpose_graph, vertex, visited, c())
151+
visited <- result$visited
152+
current_scc <- sort(result$current_scc)
153+
154+
scc_list[[scc_count]] <- current_scc
155+
156+
# Map vertices to their SCC number
157+
for (v in current_scc) {
158+
vertex_to_scc[v] <- scc_count
159+
}
160+
161+
cat("SCC", scc_count, ":", current_scc, "\n")
162+
}
163+
}
164+
165+
# Filter vertex_to_scc to only include vertices that exist in graph
166+
vertex_to_scc <- vertex_to_scc[all_vertices]
167+
168+
return(list(
169+
scc_list = scc_list,
170+
scc_count = scc_count,
171+
vertex_to_scc = vertex_to_scc,
172+
transpose_graph = transpose_graph
173+
))
174+
}
175+
176+
# Print function for SCC results
177+
print_scc_results <- function(result) {
178+
cat("\n=== KOSARAJU'S ALGORITHM RESULTS ===\n")
179+
cat("Number of Strongly Connected Components:", result$scc_count, "\n\n")
180+
181+
for (i in 1:result$scc_count) {
182+
cat("SCC", i, ":", result$scc_list[[i]], "\n")
183+
}
184+
185+
cat("\nVertex to SCC mapping:\n")
186+
for (vertex in names(result$vertex_to_scc)) {
187+
if (!is.na(result$vertex_to_scc[vertex])) {
188+
cat("Vertex", vertex, "-> SCC", result$vertex_to_scc[vertex], "\n")
189+
}
190+
}
191+
}
192+
193+
# Function to visualize graph structure (text-based)
194+
print_graph <- function(graph, title = "Graph") {
195+
cat("\n=== ", title, " ===\n")
196+
if (length(graph) == 0) {
197+
cat("Empty graph\n")
198+
return()
199+
}
200+
201+
for (vertex in names(graph)) {
202+
if (length(graph[[vertex]]) > 0) {
203+
cat("Vertex", vertex, "->", graph[[vertex]], "\n")
204+
} else {
205+
cat("Vertex", vertex, "-> (no outgoing edges)\n")
206+
}
207+
}
208+
209+
# Also show vertices with no outgoing edges
210+
all_vertices <- unique(c(names(graph), unlist(graph)))
211+
vertices_with_no_outgoing <- setdiff(all_vertices, names(graph))
212+
for (vertex in vertices_with_no_outgoing) {
213+
cat("Vertex", vertex, "-> (no outgoing edges)\n")
214+
}
215+
}
216+
217+
# ==============================================================================
218+
# EXAMPLES AND TEST CASES
219+
# ==============================================================================
220+
221+
run_kosaraju_examples <- function() {
222+
cat("=================================================================\n")
223+
cat("KOSARAJU'S ALGORITHM - STRONGLY CONNECTED COMPONENTS EXAMPLES\n")
224+
cat("=================================================================\n\n")
225+
226+
# Example 1: Simple graph with 2 SCCs
227+
cat("EXAMPLE 1: Simple Directed Graph with 2 SCCs\n")
228+
cat("-----------------------------------------------------------------\n")
229+
230+
# Graph: 1 -> 2 -> 3 -> 1 (SCC: {1,2,3}) and 4 -> 5, 5 -> 4 (SCC: {4,5})
231+
# Also: 2 -> 4 (bridge between SCCs)
232+
graph1 <- list(
233+
"1" = c(2),
234+
"2" = c(3, 4),
235+
"3" = c(1),
236+
"4" = c(5),
237+
"5" = c(4)
238+
)
239+
240+
print_graph(graph1, "Example 1 - Original Graph")
241+
result1 <- kosaraju_scc(graph1)
242+
print_scc_results(result1)
243+
244+
cat("\n=================================================================\n")
245+
cat("EXAMPLE 2: Linear Chain (No Cycles)\n")
246+
cat("-----------------------------------------------------------------\n")
247+
248+
# Graph: 1 -> 2 -> 3 -> 4 (Each vertex is its own SCC)
249+
graph2 <- list(
250+
"1" = c(2),
251+
"2" = c(3),
252+
"3" = c(4),
253+
"4" = c()
254+
)
255+
256+
print_graph(graph2, "Example 2 - Linear Chain")
257+
result2 <- kosaraju_scc(graph2)
258+
print_scc_results(result2)
259+
260+
cat("\n=================================================================\n")
261+
cat("EXAMPLE 3: Complex Graph with Multiple SCCs\n")
262+
cat("-----------------------------------------------------------------\n")
263+
264+
# More complex graph with 3 SCCs
265+
# SCC 1: {1, 2, 3} SCC 2: {4, 5, 6} SCC 3: {7}
266+
graph3 <- list(
267+
"1" = c(2),
268+
"2" = c(3, 4),
269+
"3" = c(1),
270+
"4" = c(5),
271+
"5" = c(6),
272+
"6" = c(4, 7),
273+
"7" = c()
274+
)
275+
276+
print_graph(graph3, "Example 3 - Complex Graph")
277+
result3 <- kosaraju_scc(graph3)
278+
print_scc_results(result3)
279+
280+
cat("\n=================================================================\n")
281+
cat("EXAMPLE 4: Single Strongly Connected Component\n")
282+
cat("-----------------------------------------------------------------\n")
283+
284+
# Complete cycle: 1 -> 2 -> 3 -> 4 -> 1
285+
graph4 <- list(
286+
"1" = c(2),
287+
"2" = c(3),
288+
"3" = c(4),
289+
"4" = c(1)
290+
)
291+
292+
print_graph(graph4, "Example 4 - Single SCC")
293+
result4 <- kosaraju_scc(graph4)
294+
print_scc_results(result4)
295+
296+
cat("\n=================================================================\n")
297+
cat("EXAMPLE 5: Disconnected Graph\n")
298+
cat("-----------------------------------------------------------------\n")
299+
300+
# Two separate components: {1 -> 2 -> 1} and {3 -> 4 -> 3}
301+
graph5 <- list(
302+
"1" = c(2),
303+
"2" = c(1),
304+
"3" = c(4),
305+
"4" = c(3)
306+
)
307+
308+
print_graph(graph5, "Example 5 - Disconnected Graph")
309+
result5 <- kosaraju_scc(graph5)
310+
print_scc_results(result5)
311+
312+
cat("\n=================================================================\n")
313+
cat("PRACTICAL APPLICATION: Social Network Analysis\n")
314+
cat("-----------------------------------------------------------------\n")
315+
316+
cat("In social networks, SCCs represent groups of people who can\n")
317+
cat("all reach each other through mutual connections. This is useful for:\n")
318+
cat("- Community detection\n")
319+
cat("- Information spread analysis\n")
320+
cat("- Influence maximization\n")
321+
cat("- Network segmentation\n\n")
322+
323+
# Example social network (simplified)
324+
social_network <- list(
325+
"Alice" = c("Bob"),
326+
"Bob" = c("Charlie", "David"),
327+
"Charlie" = c("Alice"), # Forms cycle Alice->Bob->Charlie->Alice
328+
"David" = c("Eve"),
329+
"Eve" = c("David"), # Forms cycle David->Eve->David
330+
"Frank" = c() # Isolated node
331+
)
332+
333+
cat("Social Network Example:\n")
334+
print_graph(social_network, "Social Network Graph")
335+
336+
# Note: This will work but vertex names will be converted to numbers
337+
cat("Note: Algorithm works with numeric vertices. For named vertices,\n")
338+
cat("you would need to create a mapping between names and numbers.\n\n")
339+
340+
cat("=================================================================\n")
341+
cat("END OF EXAMPLES\n")
342+
cat("=================================================================\n")
343+
}
344+
345+
# Utility function to convert named graph to numeric
346+
convert_named_to_numeric_graph <- function(named_graph) {
347+
# Get unique vertex names
348+
all_names <- unique(c(names(named_graph), unlist(named_graph)))
349+
350+
# Create name to number mapping
351+
name_to_num <- setNames(seq_along(all_names), all_names)
352+
num_to_name <- setNames(all_names, seq_along(all_names))
353+
354+
# Convert graph
355+
numeric_graph <- list()
356+
for (vertex_name in names(named_graph)) {
357+
vertex_num <- name_to_num[vertex_name]
358+
neighbors <- named_graph[[vertex_name]]
359+
numeric_neighbors <- name_to_num[neighbors]
360+
numeric_graph[[as.character(vertex_num)]] <- numeric_neighbors
361+
}
362+
363+
return(list(
364+
graph = numeric_graph,
365+
name_to_num = name_to_num,
366+
num_to_name = num_to_name
367+
))
368+
}
369+
370+
# Examples are available but not run automatically to avoid side effects
371+
# To run examples, execute: run_kosaraju_examples()
372+
if (interactive()) {
373+
cat("Loading Kosaraju's Strongly Connected Components Algorithm...\n")
374+
cat("Run 'run_kosaraju_examples()' to see examples and test cases.\n")
375+
}
376+
377+
# Uncomment the following line to run examples automatically:
378+
# run_kosaraju_examples()

0 commit comments

Comments
 (0)