Skip to content

Commit 9954b50

Browse files
Add Kosaraju's Strongly Connected Components algorithm (#256)
1 parent b76581c commit 9954b50

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

6264
## Linked List Algorithms

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)