Skip to content

Commit aa686ce

Browse files
authored
Implemented the Minimum Path Sum algorithm in R. (#172)
1 parent e33a9c6 commit aa686ce

File tree

1 file changed

+373
-0
lines changed

1 file changed

+373
-0
lines changed
Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
# Minimum Path Sum Problem
2+
#
3+
# The Minimum Path Sum problem finds the minimum sum path from the top-left corner
4+
# to the bottom-right corner of a grid, moving only right or down at each step.
5+
# This is a classic dynamic programming problem that appears in various forms.
6+
#
7+
# Time Complexity: O(m * n) where m = number of rows, n = number of columns
8+
# Space Complexity: O(m * n) for DP table, O(min(m, n)) for optimized version
9+
#
10+
# Applications:
11+
# - Grid-based pathfinding algorithms
12+
# - Resource optimization in 2D grids
13+
# - Game development (pathfinding with costs)
14+
# - Network routing optimization
15+
# - Cost minimization in transportation
16+
17+
# Basic DP solution for Minimum Path Sum
18+
minimum_path_sum <- function(grid) {
19+
#' Find the minimum path sum from top-left to bottom-right corner
20+
#' @param grid: 2D matrix of non-negative integers
21+
#' @return: List containing minimum sum, path, and DP table
22+
23+
m <- nrow(grid)
24+
n <- ncol(grid)
25+
26+
# Handle edge case
27+
if (m == 0 || n == 0) {
28+
return(list(
29+
min_sum = 0,
30+
path = c(),
31+
dp_table = matrix(0, nrow = 1, ncol = 1)
32+
))
33+
}
34+
35+
# Create DP table: dp[i, j] = minimum sum to reach position (i, j)
36+
dp <- matrix(0, nrow = m, ncol = n)
37+
38+
# Initialize first row and column
39+
dp[1, 1] <- grid[1, 1]
40+
41+
# Fill first row (can only move right)
42+
if (n > 1) {
43+
for (j in 2:n) {
44+
dp[1, j] <- dp[1, j - 1] + grid[1, j]
45+
}
46+
}
47+
48+
# Fill first column (can only move down)
49+
if (m > 1) {
50+
for (i in 2:m) {
51+
dp[i, 1] <- dp[i - 1, 1] + grid[i, 1]
52+
}
53+
}
54+
55+
# Fill remaining cells
56+
if (m > 1 && n > 1) {
57+
for (i in 2:m) {
58+
for (j in 2:n) {
59+
dp[i, j] <- min(dp[i - 1, j], dp[i, j - 1]) + grid[i, j]
60+
}
61+
}
62+
}
63+
64+
# Backtrack to find the path
65+
path <- list()
66+
i <- m
67+
j <- n
68+
69+
while (i > 1 || j > 1) {
70+
path <- c(list(c(i, j)), path)
71+
72+
if (i == 1) {
73+
# Can only move left
74+
j <- j - 1
75+
} else if (j == 1) {
76+
# Can only move up
77+
i <- i - 1
78+
} else {
79+
# Choose direction with minimum sum
80+
if (dp[i - 1, j] < dp[i, j - 1]) {
81+
i <- i - 1
82+
} else {
83+
j <- j - 1
84+
}
85+
}
86+
}
87+
path <- c(list(c(1, 1)), path)
88+
89+
return(list(
90+
min_sum = dp[m, n],
91+
path = path,
92+
dp_table = dp
93+
))
94+
}
95+
96+
# Space-optimized version using only 1D array
97+
minimum_path_sum_optimized <- function(grid) {
98+
#' Space optimized minimum path sum using 1D array
99+
#' @param grid: 2D matrix of non-negative integers
100+
#' @return: Minimum path sum
101+
102+
m <- nrow(grid)
103+
n <- ncol(grid)
104+
105+
if (m == 0 || n == 0) return(0)
106+
107+
# Use the smaller dimension for space optimization
108+
if (m <= n) {
109+
# Process row by row
110+
dp <- rep(0, m)
111+
dp[1] <- grid[1, 1]
112+
113+
# Initialize first row
114+
if (m > 1) {
115+
for (i in 2:m) {
116+
dp[i] <- dp[i - 1] + grid[i, 1]
117+
}
118+
}
119+
120+
# Process remaining columns
121+
for (j in 2:n) {
122+
dp[1] <- dp[1] + grid[1, j]
123+
for (i in 2:m) {
124+
dp[i] <- min(dp[i - 1], dp[i]) + grid[i, j]
125+
}
126+
}
127+
} else {
128+
# Process column by column
129+
dp <- rep(0, n)
130+
dp[1] <- grid[1, 1]
131+
132+
# Initialize first column
133+
for (j in 2:n) {
134+
dp[j] <- dp[j - 1] + grid[1, j]
135+
}
136+
137+
# Process remaining rows
138+
for (i in 2:m) {
139+
dp[1] <- dp[1] + grid[i, 1]
140+
for (j in 2:n) {
141+
dp[j] <- min(dp[j - 1], dp[j]) + grid[i, j]
142+
}
143+
}
144+
}
145+
146+
return(dp[length(dp)])
147+
}
148+
149+
# Function to find all possible minimum paths
150+
find_all_minimum_paths <- function(grid) {
151+
#' Find all possible paths that achieve the minimum sum
152+
#' @param grid: 2D matrix of non-negative integers
153+
#' @return: List of all minimum cost paths
154+
155+
m <- nrow(grid)
156+
n <- ncol(grid)
157+
158+
if (m == 0 || n == 0) return(list())
159+
160+
# First compute the minimum sum
161+
result <- minimum_path_sum(grid)
162+
min_sum <- result$min_sum
163+
164+
all_paths <- list()
165+
166+
# Use recursive backtracking to find all paths with minimum sum
167+
find_paths_recursive <- function(current_path, current_sum, i, j) {
168+
current_sum <- current_sum + grid[i, j]
169+
170+
# If we've reached the bottom-right corner
171+
if (i == m && j == n) {
172+
if (current_sum == min_sum) {
173+
all_paths <<- c(all_paths, list(c(current_path, list(c(i, j)))))
174+
}
175+
return
176+
}
177+
178+
# If current sum exceeds minimum, prune
179+
if (current_sum > min_sum) {
180+
return
181+
}
182+
183+
# Move right
184+
if (j < n) {
185+
find_paths_recursive(c(current_path, list(c(i, j))), current_sum, i, j + 1)
186+
}
187+
188+
# Move down
189+
if (i < m) {
190+
find_paths_recursive(c(current_path, list(c(i, j))), current_sum, i + 1, j)
191+
}
192+
}
193+
194+
find_paths_recursive(list(), 0, 1, 1)
195+
return(all_paths)
196+
}
197+
198+
# Helper function to print DP table
199+
print_minimum_path_sum_dp <- function(dp_table, grid) {
200+
m <- nrow(grid)
201+
n <- ncol(grid)
202+
203+
cat("DP Table for Minimum Path Sum:\n")
204+
cat("Grid:\n")
205+
for (i in 1:m) {
206+
cat(" ")
207+
for (j in 1:n) {
208+
cat(sprintf("%3d ", grid[i, j]))
209+
}
210+
cat("\n")
211+
}
212+
cat("\nDP Table:\n")
213+
for (i in 1:m) {
214+
cat(" ")
215+
for (j in 1:n) {
216+
cat(sprintf("%3d ", dp_table[i, j]))
217+
}
218+
cat("\n")
219+
}
220+
cat("\n")
221+
}
222+
223+
# Helper function to visualize path on grid
224+
visualize_path <- function(grid, path) {
225+
m <- nrow(grid)
226+
n <- ncol(grid)
227+
228+
cat("Path Visualization:\n")
229+
cat("Grid with path marked (*):\n")
230+
231+
# Create a matrix to mark the path
232+
path_matrix <- matrix(" ", nrow = m, ncol = n)
233+
234+
for (pos in path) {
235+
path_matrix[pos[1], pos[2]] <- "*"
236+
}
237+
238+
for (i in 1:m) {
239+
cat(" ")
240+
for (j in 1:n) {
241+
if (path_matrix[i, j] == "*") {
242+
cat(sprintf("%3s ", "*"))
243+
} else {
244+
cat(sprintf("%3d ", grid[i, j]))
245+
}
246+
}
247+
cat("\n")
248+
}
249+
cat("\n")
250+
}
251+
252+
# ===========================
253+
# Example Usage & Testing
254+
# ===========================
255+
cat("=== Minimum Path Sum Problem (Dynamic Programming) ===\n\n")
256+
257+
# Test 1: Basic Example
258+
cat("Test 1: Basic Example\n")
259+
grid1 <- matrix(c(1, 3, 1, 1, 5, 1, 4, 2, 1), nrow = 3, ncol = 3, byrow = TRUE)
260+
cat("Grid:\n")
261+
print(grid1)
262+
263+
result1 <- minimum_path_sum(grid1)
264+
print_minimum_path_sum_dp(result1$dp_table, grid1)
265+
cat("Minimum Path Sum:", result1$min_sum, "\n")
266+
cat("Path (row, col):", paste(sapply(result1$path, function(x) paste("(", x[1], ",", x[2], ")", sep="")), collapse = " -> "), "\n")
267+
visualize_path(grid1, result1$path)
268+
cat("\n")
269+
270+
# Test 2: Optimized Version
271+
cat("Test 2: Space Optimized Version\n")
272+
min_sum_opt <- minimum_path_sum_optimized(grid1)
273+
cat("Minimum Path Sum (Optimized):", min_sum_opt, "\n")
274+
cat("Verification: Both methods match:", result1$min_sum == min_sum_opt, "\n\n")
275+
276+
# Test 3: Single Row/Column Cases
277+
cat("Test 3: Edge Cases\n")
278+
cat("Single row grid:\n")
279+
grid_row <- matrix(c(1, 2, 3, 4, 5), nrow = 1)
280+
print(grid_row)
281+
result_row <- minimum_path_sum(grid_row)
282+
cat("Minimum sum:", result_row$min_sum, "\n\n")
283+
284+
cat("Single column grid:\n")
285+
grid_col <- matrix(c(1, 2, 3, 4, 5), ncol = 1)
286+
print(grid_col)
287+
result_col <- minimum_path_sum(grid_col)
288+
cat("Minimum sum:", result_col$min_sum, "\n\n")
289+
290+
# Test 4: Larger Grid
291+
cat("Test 4: Larger Grid (4x5)\n")
292+
# Set random seed for reproducibility in tests. The value 42 is chosen arbitrarily.
293+
SEED <- 42
294+
set.seed(SEED)
295+
grid_large <- matrix(sample(1:9, 20, replace = TRUE), nrow = 4, ncol = 5)
296+
cat("Grid:\n")
297+
print(grid_large)
298+
299+
result_large <- minimum_path_sum(grid_large)
300+
cat("Minimum Path Sum:", result_large$min_sum, "\n")
301+
cat("Path length:", length(result_large$path), "steps\n")
302+
visualize_path(grid_large, result_large$path)
303+
cat("\n")
304+
305+
# Test 5: Performance Comparison
306+
cat("Test 5: Performance Comparison (6x8 grid)\n")
307+
grid_perf <- matrix(sample(1:20, 48, replace = TRUE), nrow = 6, ncol = 8)
308+
309+
library(microbenchmark)
310+
311+
mbm <- microbenchmark(
312+
std = minimum_path_sum(grid_perf),
313+
opt = minimum_path_sum_optimized(grid_perf),
314+
times = 100L
315+
)
316+
317+
result_std <- minimum_path_sum(grid_perf)
318+
result_opt <- minimum_path_sum_optimized(grid_perf)
319+
320+
cat("Standard DP result:", result_std$min_sum, "\n")
321+
cat("Optimized DP result:", result_opt, "\n")
322+
cat("Standard DP median time:", sprintf("%.6f sec", median(mbm$time[mbm$expr == "std"])/1e9), "\n")
323+
cat("Optimized DP median time:", sprintf("%.6f sec", median(mbm$time[mbm$expr == "opt"])/1e9), "\n")
324+
cat("Results match:", result_std$min_sum == result_opt, "\n\n")
325+
326+
# Test 6: Multiple Minimum Paths
327+
cat("Test 6: Multiple Minimum Paths\n")
328+
grid_multiple <- matrix(c(1, 2, 1, 1, 1, 1, 1, 1, 1), nrow = 3, ncol = 3, byrow = TRUE)
329+
cat("Grid:\n")
330+
print(grid_multiple)
331+
332+
result_multiple <- minimum_path_sum(grid_multiple)
333+
cat("Minimum Path Sum:", result_multiple$min_sum, "\n")
334+
cat("One possible path:", paste(sapply(result_multiple$path, function(x) paste("(", x[1], ",", x[2], ")", sep="")), collapse = " -> "), "\n")
335+
336+
# Find all minimum paths
337+
all_paths <- find_all_minimum_paths(grid_multiple)
338+
cat("Total number of minimum paths:", length(all_paths), "\n")
339+
for (i in seq_along(all_paths)) {
340+
path_str <- paste(sapply(all_paths[[i]], function(x) paste("(", x[1], ",", x[2], ")", sep="")), collapse = " -> ")
341+
path_sum <- sum(sapply(all_paths[[i]], function(x) grid_multiple[x[1], x[2]]))
342+
cat("Path", i, ":", path_str, "(sum =", path_sum, ")\n")
343+
}
344+
cat("\n")
345+
346+
# Test 7: Real-world Example - Cost Optimization
347+
cat("Test 7: Real-world Example - Transportation Cost Optimization\n")
348+
# Grid representing transportation costs between cities
349+
transport_grid <- matrix(c(
350+
2, 3, 4, 2, 1,
351+
1, 2, 1, 3, 2,
352+
3, 1, 2, 1, 4,
353+
2, 4, 1, 2, 3
354+
), nrow = 4, ncol = 5, byrow = TRUE)
355+
356+
cat("Transportation Cost Grid:\n")
357+
print(transport_grid)
358+
359+
transport_result <- minimum_path_sum(transport_grid)
360+
cat("Minimum transportation cost:", transport_result$min_sum, "\n")
361+
cat("Optimal route:", paste(sapply(transport_result$path, function(x) paste("City(", x[1], ",", x[2], ")", sep="")), collapse = " -> "), "\n")
362+
visualize_path(transport_grid, transport_result$path)
363+
364+
# Calculate cost breakdown
365+
cat("Cost breakdown:\n")
366+
total_cost <- 0
367+
for (i in seq_along(transport_result$path)) {
368+
pos <- transport_result$path[[i]]
369+
cost <- transport_grid[pos[1], pos[2]]
370+
total_cost <- total_cost + cost
371+
cat(" Step", i, ": City(", pos[1], ",", pos[2], ") =", cost, "\n")
372+
}
373+
cat("Total cost verification:", total_cost, "\n")

0 commit comments

Comments
 (0)