1+ # bridge_detector.r
2+ # Bridge Detection Algorithm in R using Tarjan's Algorithm
3+ # Finds all critical edges (bridges) in an undirected graph.
4+ # A bridge is an edge whose removal increases the number of connected components.
5+ #
6+ # Algorithm details:
7+ # - Uses DFS traversal with discovery time and low-link values
8+ # - disc[v]: Discovery time of vertex v in DFS
9+ # - low[v]: Earliest discovered vertex reachable from v's subtree
10+ # - Bridge condition: For edge (u,v), if low[v] > disc[u], then (u,v) is a bridge
11+ # - Time complexity: O(V + E) where V = vertices, E = edges
12+ # - Space complexity: O(V) for visited, discovery, and low-link arrays
13+
14+ library(R6 )
15+
16+ BridgeDetector <- R6Class(
17+ " BridgeDetector" ,
18+
19+ public = list (
20+ vertices = NULL ,
21+ graph = NULL ,
22+ time_counter = NULL ,
23+
24+ initialize = function (n_vertices ) {
25+ " Initialize the bridge detector with specified number of vertices"
26+ if (! is.numeric(n_vertices ) || n_vertices < 0 || n_vertices != round(n_vertices )) {
27+ stop(" Number of vertices must be a non-negative integer" )
28+ }
29+ self $ vertices <- n_vertices
30+ self $ graph <- vector(" list" , n_vertices )
31+ self $ time_counter <- 0
32+
33+ for (i in seq_len(n_vertices )) {
34+ self $ graph [[i ]] <- integer(0 )
35+ }
36+ },
37+
38+ add_edge = function (u , v ) {
39+ " Add an undirected edge between vertices u and v (0-indexed)"
40+ if (! is.numeric(u ) || ! is.numeric(v ) ||
41+ u < 0 || v < 0 ||
42+ u > = self $ vertices || v > = self $ vertices ||
43+ u != round(u ) || v != round(v )) {
44+ stop(" Vertex indices must be integers in range [0, n_vertices-1]" )
45+ }
46+ u_idx <- u + 1
47+ v_idx <- v + 1
48+ self $ graph [[u_idx ]] <- c(self $ graph [[u_idx ]], v_idx )
49+ self $ graph [[v_idx ]] <- c(self $ graph [[v_idx ]], u_idx )
50+ },
51+
52+ find_bridges = function () {
53+ " Find all bridges in the graph using Tarjan's algorithm"
54+ visited <- rep(FALSE , self $ vertices )
55+ disc <- rep(Inf , self $ vertices )
56+ low <- rep(Inf , self $ vertices )
57+ parent <- rep(- 1 , self $ vertices )
58+ bridges <- list ()
59+
60+ self $ time_counter <- 0
61+
62+ for (v in seq_len(self $ vertices )) {
63+ if (! visited [v ]) {
64+ res <- private $ dfs_bridge(v , visited , disc , low , parent , bridges )
65+ visited <- res $ visited
66+ disc <- res $ disc
67+ low <- res $ low
68+ parent <- res $ parent
69+ bridges <- res $ bridges
70+ }
71+ }
72+ return (bridges )
73+ },
74+
75+ print_graph = function () {
76+ " Print adjacency list of the graph (0-indexed)"
77+ cat(" Graph Adjacency List:\n " )
78+ for (i in seq_len(self $ vertices )) {
79+ neighbors <- if (length(self $ graph [[i ]]) > 0 ) {
80+ paste(self $ graph [[i ]] - 1 , collapse = " , " )
81+ } else {
82+ " none"
83+ }
84+ cat(sprintf(" Vertex %d: %s\n " , i - 1 , neighbors ))
85+ }
86+ }
87+ ),
88+
89+ private = list (
90+ dfs_bridge = function (u , visited , disc , low , parent , bridges ) {
91+ visited [u ] <- TRUE
92+ disc [u ] <- self $ time_counter
93+ low [u ] <- self $ time_counter
94+ self $ time_counter <- self $ time_counter + 1
95+
96+ for (v_idx in self $ graph [[u ]]) {
97+ if (! visited [v_idx ]) {
98+ parent [v_idx ] <- u
99+ res <- private $ dfs_bridge(v_idx , visited , disc , low , parent , bridges )
100+ visited <- res $ visited
101+ disc <- res $ disc
102+ low <- res $ low
103+ parent <- res $ parent
104+ bridges <- res $ bridges
105+
106+ low [u ] <- min(low [u ], low [v_idx ])
107+
108+ if (low [v_idx ] > disc [u ]) {
109+ bridges [[length(bridges ) + 1 ]] <- c(u - 1 , v_idx - 1 )
110+ }
111+ } else if (v_idx != parent [u ]) {
112+ low [u ] <- min(low [u ], disc [v_idx ])
113+ }
114+ }
115+
116+ return (list (
117+ visited = visited ,
118+ disc = disc ,
119+ low = low ,
120+ parent = parent ,
121+ bridges = bridges
122+ ))
123+ }
124+ )
125+ )
126+
127+ # Demonstration
128+ demonstrate_bridge_detection <- function () {
129+ cat(" === Bridge Detection Algorithm Demo ===\n\n " )
130+
131+ # Example 1
132+ cat(" Example 1: Simple network with bridges\n " )
133+ cat(" Graph: 0-1-2-3\n " )
134+ cat(" | |\n " )
135+ cat(" 4 5\n\n " )
136+
137+ detector1 <- BridgeDetector $ new(6 )
138+ detector1 $ add_edge(0 , 1 )
139+ detector1 $ add_edge(1 , 2 )
140+ detector1 $ add_edge(2 , 3 )
141+ detector1 $ add_edge(0 , 4 )
142+ detector1 $ add_edge(2 , 5 )
143+
144+ bridges1 <- detector1 $ find_bridges()
145+ cat(" Bridges found:\n " )
146+ for (b in bridges1 ) {
147+ cat(sprintf(" (%d, %d)\n " , b [1 ], b [2 ]))
148+ }
149+ cat(" All edges are critical - removing any disconnects the network.\n\n " )
150+
151+ # Example 2
152+ cat(" Example 2: Network with cycle (no bridges)\n " )
153+ cat(" Graph: 0-1-2\n " )
154+ cat(" | |\n " )
155+ cat(" +---+\n\n " )
156+
157+ detector2 <- BridgeDetector $ new(3 )
158+ detector2 $ add_edge(0 , 1 )
159+ detector2 $ add_edge(1 , 2 )
160+ detector2 $ add_edge(2 , 0 )
161+
162+ bridges2 <- detector2 $ find_bridges()
163+ if (length(bridges2 ) == 0 ) {
164+ cat(" Bridges found: None\n " )
165+ cat(" The cycle provides redundancy - no single edge is critical.\n\n " )
166+ }
167+
168+ # Example 3
169+ cat(" Example 3: Complex network topology\n " )
170+ detector3 <- BridgeDetector $ new(7 )
171+ detector3 $ add_edge(0 , 1 )
172+ detector3 $ add_edge(1 , 2 )
173+ detector3 $ add_edge(2 , 0 )
174+ detector3 $ add_edge(1 , 3 )
175+ detector3 $ add_edge(3 , 4 )
176+ detector3 $ add_edge(4 , 5 )
177+ detector3 $ add_edge(5 , 6 )
178+ detector3 $ add_edge(6 , 4 )
179+
180+ bridges3 <- detector3 $ find_bridges()
181+ cat(" Bridges found:\n " )
182+ for (b in bridges3 ) {
183+ cat(sprintf(" (%d, %d)\n " , b [1 ], b [2 ]))
184+ }
185+ cat(" Edge (1,3) connects two robust sub-networks.\n\n " )
186+
187+ # Example 4: Testing print_graph
188+ cat(" Example 4: Viewing graph structure\n " )
189+ detector4 <- BridgeDetector $ new(4 )
190+ detector4 $ add_edge(0 , 1 )
191+ detector4 $ add_edge(1 , 2 )
192+ detector4 $ add_edge(2 , 3 )
193+ detector4 $ print_graph()
194+ cat(" \n " )
195+
196+ # Example 5: Edge cases
197+ cat(" Example 5: Edge cases\n " )
198+
199+ # Empty graph
200+ detector5 <- BridgeDetector $ new(0 )
201+ bridges5 <- detector5 $ find_bridges()
202+ cat(" Empty graph bridges: " , length(bridges5 ), " \n " )
203+
204+ # Single edge
205+ detector6 <- BridgeDetector $ new(2 )
206+ detector6 $ add_edge(0 , 1 )
207+ bridges6 <- detector6 $ find_bridges()
208+ cat(" Single edge graph bridges:\n " )
209+ for (b in bridges6 ) {
210+ cat(sprintf(" (%d, %d)\n " , b [1 ], b [2 ]))
211+ }
212+
213+ # Disconnected components
214+ detector7 <- BridgeDetector $ new(4 )
215+ detector7 $ add_edge(0 , 1 )
216+ detector7 $ add_edge(2 , 3 )
217+ bridges7 <- detector7 $ find_bridges()
218+ cat(" Disconnected components bridges:\n " )
219+ for (b in bridges7 ) {
220+ cat(sprintf(" (%d, %d)\n " , b [1 ], b [2 ]))
221+ }
222+ cat(" \n " )
223+ }
224+
225+ # Run demo
226+ demonstrate_bridge_detection()
0 commit comments