Skip to content

Commit e6c0b52

Browse files
authored
Tarjan's Bridge Finding Algorithm (#198)
1 parent b811a36 commit e6c0b52

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed

graph_algorithms/bridge_detector.r

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
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

Comments
 (0)