Skip to content

Commit c9dbef5

Browse files
committed
Add -Dinic’s Algorithm
1 parent b083bc9 commit c9dbef5

1 file changed

Lines changed: 388 additions & 0 deletions

File tree

graph_algorithms/dinic_max_flow.r

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
# Dinic's Algorithm for Maximum Flow
2+
#
3+
# Dinic's algorithm finds the maximum flow in a flow network from source to sink.
4+
# It uses level graphs and blocking flows to achieve better time complexity than
5+
# Ford-Fulkerson. The algorithm repeatedly finds shortest augmenting paths using BFS
6+
# and then uses DFS to send flow along multiple paths in each iteration.
7+
#
8+
# Time Complexity: O(V^2 * E) general case, O(E * sqrt(V)) for unit capacity networks
9+
# Space Complexity: O(V + E) for adjacency list and level array
10+
#
11+
# Applications:
12+
# - Network routing and traffic optimization
13+
# - Bipartite matching problems
14+
# - Image segmentation
15+
# - Airline scheduling
16+
# - Project selection and resource allocation
17+
18+
#' Create an empty flow network
19+
#' @param n: Number of vertices
20+
#' @return: Flow network structure with adjacency list and capacity matrix
21+
create_flow_network <- function(n) {
22+
list(
23+
n = n,
24+
graph = vector("list", n), # Adjacency list
25+
capacity = matrix(0, nrow = n, ncol = n), # Capacity matrix
26+
flow = matrix(0, nrow = n, ncol = n) # Flow matrix
27+
)
28+
}
29+
30+
#' Add edge to flow network
31+
#' @param network: Flow network structure
32+
#' @param from: Source vertex (1-indexed)
33+
#' @param to: Destination vertex (1-indexed)
34+
#' @param capacity: Edge capacity
35+
#' @return: Updated network
36+
add_edge <- function(network, from, to, capacity) {
37+
# Add forward edge
38+
network$graph[[from]] <- c(network$graph[[from]], to)
39+
network$capacity[from, to] <- network$capacity[from, to] + capacity
40+
41+
# Add reverse edge for residual graph (with 0 capacity initially)
42+
if (!(from %in% network$graph[[to]])) {
43+
network$graph[[to]] <- c(network$graph[[to]], from)
44+
}
45+
46+
return(network)
47+
}
48+
49+
#' Build level graph using BFS
50+
#' @param network: Flow network
51+
#' @param source: Source vertex
52+
#' @param sink: Sink vertex
53+
#' @return: Level array (-1 if vertex not reachable)
54+
bfs_level_graph <- function(network, source, sink) {
55+
n <- network$n
56+
level <- rep(-1, n)
57+
level[source] <- 0
58+
59+
queue <- c(source)
60+
61+
while (length(queue) > 0) {
62+
u <- queue[1]
63+
queue <- queue[-1]
64+
65+
for (v in network$graph[[u]]) {
66+
# Check if edge has residual capacity and v is not visited
67+
residual_capacity <- network$capacity[u, v] - network$flow[u, v]
68+
69+
if (level[v] == -1 && residual_capacity > 0) {
70+
level[v] <- level[u] + 1
71+
queue <- c(queue, v)
72+
}
73+
}
74+
}
75+
76+
return(level)
77+
}
78+
79+
#' Send flow using DFS (blocking flow)
80+
#' @param network: Flow network
81+
#' @param u: Current vertex
82+
#' @param sink: Sink vertex
83+
#' @param level: Level array from BFS
84+
#' @param flow: Flow to push
85+
#' @param start: Array tracking next edge to try from each vertex
86+
#' @return: Flow pushed
87+
dfs_send_flow <- function(network, u, sink, level, flow, start) {
88+
# Reached sink
89+
if (u == sink) {
90+
return(flow)
91+
}
92+
93+
# Try all edges from current vertex
94+
while (start[u] <= length(network$graph[[u]])) {
95+
v <- network$graph[[u]][start[u]]
96+
residual_capacity <- network$capacity[u, v] - network$flow[u, v]
97+
98+
# Check if this edge can be used (level increases and has capacity)
99+
if (level[v] == level[u] + 1 && residual_capacity > 0) {
100+
# Recursively send flow
101+
pushed_flow <- dfs_send_flow(
102+
network,
103+
v,
104+
sink,
105+
level,
106+
min(flow, residual_capacity),
107+
start
108+
)
109+
110+
if (pushed_flow > 0) {
111+
# Update flow
112+
network$flow[u, v] <<- network$flow[u, v] + pushed_flow
113+
network$flow[v, u] <<- network$flow[v, u] - pushed_flow
114+
return(pushed_flow)
115+
}
116+
}
117+
118+
start[u] <- start[u] + 1
119+
}
120+
121+
return(0)
122+
}
123+
124+
#' Dinic's algorithm to find maximum flow
125+
#' @param network: Flow network structure
126+
#' @param source: Source vertex (1-indexed)
127+
#' @param sink: Sink vertex (1-indexed)
128+
#' @return: List with max_flow value, flow matrix, and flow decomposition
129+
dinic_max_flow <- function(network, source, sink) {
130+
if (source < 1 || source > network$n || sink < 1 || sink > network$n) {
131+
stop("Error: Source and sink must be between 1 and n")
132+
}
133+
134+
if (source == sink) {
135+
stop("Error: Source and sink must be different")
136+
}
137+
138+
max_flow <- 0
139+
iterations <- 0
140+
141+
# Repeat until no augmenting path exists
142+
repeat {
143+
iterations <- iterations + 1
144+
145+
# Build level graph using BFS
146+
level <- bfs_level_graph(network, source, sink)
147+
148+
# If sink is not reachable, no more augmenting paths
149+
if (level[sink] == -1) {
150+
break
151+
}
152+
153+
# Send flow using DFS until blocking flow is achieved
154+
start <- rep(1, network$n) # Track next edge to try from each vertex
155+
156+
repeat {
157+
flow <- dfs_send_flow(network, source, sink, level, Inf, start)
158+
159+
if (flow == 0) {
160+
break
161+
}
162+
163+
max_flow <- max_flow + flow
164+
}
165+
}
166+
167+
# Find minimum cut
168+
level_final <- bfs_level_graph(network, source, sink)
169+
source_side <- which(level_final != -1)
170+
sink_side <- which(level_final == -1)
171+
172+
# Find edges in the minimum cut
173+
min_cut_edges <- list()
174+
for (u in source_side) {
175+
for (v in network$graph[[u]]) {
176+
if (v %in% sink_side && network$capacity[u, v] > 0) {
177+
min_cut_edges <- c(min_cut_edges, list(list(from = u, to = v, capacity = network$capacity[u, v])))
178+
}
179+
}
180+
}
181+
182+
return(list(
183+
max_flow = max_flow,
184+
flow_matrix = network$flow,
185+
iterations = iterations,
186+
min_cut_edges = min_cut_edges,
187+
source_partition = source_side,
188+
sink_partition = sink_side
189+
))
190+
}
191+
192+
#' Print maximum flow results
193+
#' @param result: Result from dinic_max_flow
194+
#' @param network: Original network (optional, for displaying edges)
195+
print_max_flow <- function(result, network = NULL) {
196+
cat("Maximum Flow Result:\n")
197+
cat(strrep("=", 60), "\n\n")
198+
cat(sprintf("Maximum Flow: %g\n", result$max_flow))
199+
cat(sprintf("Iterations: %d\n\n", result$iterations))
200+
201+
cat("Minimum Cut Edges:\n")
202+
cat(strrep("-", 60), "\n")
203+
if (length(result$min_cut_edges) > 0) {
204+
cat(sprintf("%-15s %-15s %-15s\n", "From", "To", "Capacity"))
205+
cat(strrep("-", 60), "\n")
206+
total_cut_capacity <- 0
207+
for (edge in result$min_cut_edges) {
208+
cat(sprintf("%-15d %-15d %-15g\n", edge$from, edge$to, edge$capacity))
209+
total_cut_capacity <- total_cut_capacity + edge$capacity
210+
}
211+
cat(strrep("-", 60), "\n")
212+
cat(sprintf("Total Cut Capacity: %g\n", total_cut_capacity))
213+
} else {
214+
cat("No cut edges found\n")
215+
}
216+
217+
cat(sprintf("\nSource partition: {%s}\n", paste(result$source_partition, collapse = ", ")))
218+
cat(sprintf("Sink partition: {%s}\n", paste(result$sink_partition, collapse = ", ")))
219+
cat("\n")
220+
}
221+
222+
#' Print flow on edges
223+
#' @param network: Flow network with computed flows
224+
print_flow_edges <- function(network) {
225+
cat("Flow on Edges:\n")
226+
cat(strrep("-", 60), "\n")
227+
cat(sprintf("%-10s %-10s %-12s %-12s\n", "From", "To", "Flow", "Capacity"))
228+
cat(strrep("-", 60), "\n")
229+
230+
for (u in 1:network$n) {
231+
for (v in network$graph[[u]]) {
232+
if (network$capacity[u, v] > 0 && network$flow[u, v] > 0) {
233+
cat(sprintf("%-10d %-10d %-12g %-12g\n",
234+
u, v, network$flow[u, v], network$capacity[u, v]))
235+
}
236+
}
237+
}
238+
cat(strrep("-", 60), "\n\n")
239+
}
240+
241+
# ========== Example 1: Basic 6-Vertex Network ==========
242+
243+
cat("========== Example 1: Basic 6-Vertex Flow Network ==========\n\n")
244+
245+
# Create network with 6 vertices
246+
network1 <- create_flow_network(6)
247+
248+
# Add edges (from, to, capacity)
249+
network1 <- add_edge(network1, 1, 2, 16)
250+
network1 <- add_edge(network1, 1, 3, 13)
251+
network1 <- add_edge(network1, 2, 3, 10)
252+
network1 <- add_edge(network1, 2, 4, 12)
253+
network1 <- add_edge(network1, 3, 2, 4)
254+
network1 <- add_edge(network1, 3, 5, 14)
255+
network1 <- add_edge(network1, 4, 3, 9)
256+
network1 <- add_edge(network1, 4, 6, 20)
257+
network1 <- add_edge(network1, 5, 4, 7)
258+
network1 <- add_edge(network1, 5, 6, 4)
259+
260+
cat("Network edges:\n")
261+
cat("1 -> 2 (16), 1 -> 3 (13)\n")
262+
cat("2 -> 3 (10), 2 -> 4 (12)\n")
263+
cat("3 -> 2 (4), 3 -> 5 (14)\n")
264+
cat("4 -> 3 (9), 4 -> 6 (20)\n")
265+
cat("5 -> 4 (7), 5 -> 6 (4)\n\n")
266+
267+
# Find maximum flow from vertex 1 to vertex 6
268+
result1 <- dinic_max_flow(network1, source = 1, sink = 6)
269+
print_max_flow(result1, network1)
270+
print_flow_edges(network1)
271+
272+
# ========== Example 2: Simple Network ==========
273+
274+
cat("========== Example 2: Simple 4-Vertex Network ==========\n\n")
275+
276+
network2 <- create_flow_network(4)
277+
network2 <- add_edge(network2, 1, 2, 10)
278+
network2 <- add_edge(network2, 1, 3, 10)
279+
network2 <- add_edge(network2, 2, 3, 2)
280+
network2 <- add_edge(network2, 2, 4, 4)
281+
network2 <- add_edge(network2, 3, 4, 9)
282+
283+
cat("Network edges:\n")
284+
cat("1 -> 2 (10), 1 -> 3 (10)\n")
285+
cat("2 -> 3 (2), 2 -> 4 (4)\n")
286+
cat("3 -> 4 (9)\n\n")
287+
288+
result2 <- dinic_max_flow(network2, source = 1, sink = 4)
289+
print_max_flow(result2, network2)
290+
print_flow_edges(network2)
291+
292+
# ========== Example 3: Bipartite Matching ==========
293+
294+
cat("========== Example 3: Bipartite Matching (5 workers, 4 jobs) ==========\n\n")
295+
296+
# Create bipartite graph: source(1) -> workers(2-6) -> jobs(7-10) -> sink(11)
297+
network3 <- create_flow_network(11)
298+
299+
# Source to workers (capacity 1 each)
300+
for (i in 2:6) {
301+
network3 <- add_edge(network3, 1, i, 1)
302+
}
303+
304+
# Workers to jobs (based on which jobs they can do)
305+
network3 <- add_edge(network3, 2, 7, 1) # Worker 1 can do Job 1
306+
network3 <- add_edge(network3, 2, 8, 1) # Worker 1 can do Job 2
307+
network3 <- add_edge(network3, 3, 7, 1) # Worker 2 can do Job 1
308+
network3 <- add_edge(network3, 3, 9, 1) # Worker 2 can do Job 3
309+
network3 <- add_edge(network3, 4, 8, 1) # Worker 3 can do Job 2
310+
network3 <- add_edge(network3, 4, 10, 1) # Worker 3 can do Job 4
311+
network3 <- add_edge(network3, 5, 9, 1) # Worker 4 can do Job 3
312+
network3 <- add_edge(network3, 6, 10, 1) # Worker 5 can do Job 4
313+
314+
# Jobs to sink (capacity 1 each)
315+
for (i in 7:10) {
316+
network3 <- add_edge(network3, i, 11, 1)
317+
}
318+
319+
cat("Bipartite matching problem:\n")
320+
cat("Workers: 1-5, Jobs: 1-4\n")
321+
cat("Maximum number of worker-job assignments:\n\n")
322+
323+
result3 <- dinic_max_flow(network3, source = 1, sink = 11)
324+
cat(sprintf("Maximum Matching: %d\n\n", result3$max_flow))
325+
326+
# Show assignments
327+
cat("Worker-Job Assignments:\n")
328+
for (worker in 2:6) {
329+
for (job in 7:10) {
330+
if (network3$flow[worker, job] > 0) {
331+
cat(sprintf("Worker %d -> Job %d\n", worker - 1, job - 6))
332+
}
333+
}
334+
}
335+
cat("\n")
336+
337+
# ========== Example 4: Multi-source Multi-sink ==========
338+
339+
cat("========== Example 4: Multi-Source Multi-Sink Problem ==========\n\n")
340+
341+
# Convert to single source/sink by adding super source and super sink
342+
network4 <- create_flow_network(8)
343+
344+
# Super source (1) to sources (2, 3)
345+
network4 <- add_edge(network4, 1, 2, 10)
346+
network4 <- add_edge(network4, 1, 3, 15)
347+
348+
# Internal edges
349+
network4 <- add_edge(network4, 2, 4, 8)
350+
network4 <- add_edge(network4, 2, 5, 6)
351+
network4 <- add_edge(network4, 3, 4, 5)
352+
network4 <- add_edge(network4, 3, 5, 12)
353+
network4 <- add_edge(network4, 4, 6, 10)
354+
network4 <- add_edge(network4, 5, 7, 9)
355+
356+
# Sinks (6, 7) to super sink (8)
357+
network4 <- add_edge(network4, 6, 8, 15)
358+
network4 <- add_edge(network4, 7, 8, 12)
359+
360+
cat("Multi-source multi-sink converted to single source/sink\n")
361+
cat("Sources: 2, 3 | Sinks: 6, 7\n\n")
362+
363+
result4 <- dinic_max_flow(network4, source = 1, sink = 8)
364+
print_max_flow(result4, network4)
365+
366+
# ========== Algorithm Properties ==========
367+
368+
cat("========== Algorithm Properties ==========\n\n")
369+
cat("1. Dinic's algorithm guarantees finding maximum flow\n")
370+
cat("2. Works on directed graphs with non-negative capacities\n")
371+
cat("3. Faster than Ford-Fulkerson for dense graphs\n")
372+
cat("4. Maximum flow equals minimum cut capacity (Max-Flow Min-Cut Theorem)\n")
373+
cat("5. Each iteration increases distance to sink for at least one vertex\n")
374+
cat("6. Number of iterations is O(V) for unit capacity networks\n\n")
375+
376+
cat("========== Comparison with Other Max Flow Algorithms ==========\n\n")
377+
cat("Ford-Fulkerson:\n")
378+
cat(" Time: O(E * max_flow) - can be slow for large flows\n")
379+
cat(" Method: Find any augmenting path\n\n")
380+
cat("Edmonds-Karp:\n")
381+
cat(" Time: O(V * E^2) - specific Ford-Fulkerson with BFS\n")
382+
cat(" Method: Shortest augmenting path\n\n")
383+
cat("Dinic's Algorithm:\n")
384+
cat(" Time: O(V^2 * E), O(E * sqrt(V)) for unit capacities\n")
385+
cat(" Method: Blocking flows in level graphs\n\n")
386+
cat("Push-Relabel:\n")
387+
cat(" Time: O(V^2 * E) or O(V^3) depending on implementation\n")
388+
cat(" Method: Preflow-push operations\n")

0 commit comments

Comments
 (0)