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(" \n Vertex 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