diff --git a/documentation/boruvka_mst.md b/documentation/boruvka_mst.md new file mode 100644 index 00000000..5f958d53 --- /dev/null +++ b/documentation/boruvka_mst.md @@ -0,0 +1,26 @@ +# Boruvka's Minimum Spanning Tree (MST) + +This document describes the Boruvka MST implementation located at `R/graph_algorithms/boruvka_mst.r`. + +## Description + +The implementation builds a Minimum Spanning Tree for an undirected weighted graph using Boruvka's method. The graph is represented by a list with `V` (number of vertices) and `edges` (a data.frame with columns `u`, `v`, `w`). Vertex indices are 1-based to match other algorithms in the repository. + +## Usage + +In an R session: + +source('graph_algorithms/boruvka_mst.r') + +From command line using Rscript: + +Rscript -e "source('R/graph_algorithms/boruvka_mst.r')" + +## Complexity + +- Time complexity: Depends on implementation details; this simple version iterates until components merge. +- Space complexity: O(V + E) + +## Notes + +- This implementation prioritizes clarity and repository consistency. For large graphs, more optimized data structures and path compression in union-find should be used. diff --git a/documentation/fast_fourier_transform.md b/documentation/fast_fourier_transform.md new file mode 100644 index 00000000..c51daa92 --- /dev/null +++ b/documentation/fast_fourier_transform.md @@ -0,0 +1,29 @@ +# Fast Fourier Transform (FFT) + +This file documents the recursive Cooley-Tukey FFT implementation added to `R/mathematics/fast_fourier_transform.r`. + +## Description + +The `fft_recursive` function computes the discrete Fourier transform (DFT) of a numeric or complex vector using a divide-and-conquer Cooley-Tukey algorithm. If the input length is not a power of two, it is zero-padded to the next power of two. + +## Usage + +In an R session: + +source('mathematics/fast_fourier_transform.r') +fft_recursive(c(0, 1, 2, 3)) + +From the command line with Rscript: + +Rscript -e "source('R/mathematics/fast_fourier_transform.r'); print(fft_recursive(c(0,1,2,3)))" + +## Complexity + +Time complexity: O(n log n) for inputs with length a power of two; otherwise dominated by padding to next power of two. + +Space complexity: O(n) additional space for recursive calls. + +## Notes + +- The function returns a complex vector of the same length (after padding) as the input. +- This implementation is primarily educational; production code should prefer the optimized `fft` function available in base R. diff --git a/documentation/hamiltonian_path.md b/documentation/hamiltonian_path.md new file mode 100644 index 00000000..9f5a7a37 --- /dev/null +++ b/documentation/hamiltonian_path.md @@ -0,0 +1,27 @@ +# Hamiltonian Path (Backtracking) + +This document describes the Hamiltonian Path backtracking implementation in `R/graph_algorithms/hamiltonian_path.r`. + +## Description + +The `hamiltonianPath` function searches for a Hamiltonian Path in an undirected graph represented by an adjacency matrix. It uses backtracking to attempt to build a path that visits every vertex exactly once. + +## Usage + +In an R session: + +source('graph_algorithms/hamiltonian_path.r') + +From command line using Rscript: + +Rscript -e "source('R/graph_algorithms/hamiltonian_path.r')" + +## Complexity + +- Time complexity: O(n!) in the worst case (backtracking over permutations). +- Space complexity: O(n) for path storage and recursion. + +## Notes + +- The implementation assumes an undirected graph given as an adjacency matrix with 0/1 entries. +- For production use on larger graphs, consider heuristics or approximation algorithms; the problem is NP-complete. diff --git a/documentation/push_relabel.md b/documentation/push_relabel.md new file mode 100644 index 00000000..491b0e96 --- /dev/null +++ b/documentation/push_relabel.md @@ -0,0 +1,27 @@ +# Push-Relabel (Preflow-Push) Maximum Flow + +This document describes the Push-Relabel implementation located at `R/graph_algorithms/push_relabel.r`. + +## Description + +The `push_relabel` function implements the Push-Relabel algorithm (also known as Preflow-Push) for computing maximum flow in a directed graph represented by a capacity matrix. It uses the highest-label selection rule by moving active vertices to the front of the list when relabeled. + +## Usage + +In an R session: + +source('graph_algorithms/push_relabel.r') + +From command line using Rscript: + +Rscript -e "source('R/graph_algorithms/push_relabel.r')" + +## Complexity + +- Time complexity: O(V^3) for the generic implementation; improvements (gap relabeling, global relabel) reduce this significantly for practical graphs. +- Space complexity: O(V^2) for flow and capacity matrices. + +## Notes + +- The implementation uses 1-based indexing to be consistent with other algorithms in the repository. +- For large graphs consider adding optimizations such as gap relabeling or global relabeling. diff --git a/graph_algorithms/boruvka_mst.r b/graph_algorithms/boruvka_mst.r new file mode 100644 index 00000000..1f087cd7 --- /dev/null +++ b/graph_algorithms/boruvka_mst.r @@ -0,0 +1,142 @@ +# Boruvka's Minimum Spanning Tree (MST) — improved R translation +# +# Converted from an improved Python implementation: adds path compression in +# union-find, returns whether a union happened, and guards against infinite +# loops on disconnected graphs. + +create_graph <- function(V) { + list(V = as.integer(V), edges = data.frame(u = integer(), v = integer(), w = double(), stringsAsFactors = FALSE)) +} + +add_edge <- function(graph, u, v, w) { + # Append an edge. Vertices are 1-based indices for consistency. + graph$edges <- rbind(graph$edges, data.frame(u = as.integer(u), v = as.integer(v), w = as.numeric(w), stringsAsFactors = FALSE)) + graph +} + +boruvka_mst <- function(graph) { + V <- as.integer(graph$V) + edges <- graph$edges + + # Union-Find arrays + parent <- seq_len(V) + rank <- rep(0L, V) + + find_set <- function(i) { + # Iterative find with path compression + root <- i + while (parent[root] != root) { + root <- parent[root] + } + # path compression + j <- i + while (parent[j] != root) { + nextj <- parent[j] + parent[j] <<- root + j <- nextj + } + root + } + + union_set <- function(x, y) { + xroot <- find_set(x) + yroot <- find_set(y) + if (xroot == yroot) return(FALSE) + if (rank[xroot] < rank[yroot]) { + parent[xroot] <<- yroot + } else if (rank[xroot] > rank[yroot]) { + parent[yroot] <<- xroot + } else { + parent[yroot] <<- xroot + rank[xroot] <<- rank[xroot] + 1L + } + TRUE + } + + num_trees <- V + mst_weight <- 0 + mst_edges <- data.frame(u = integer(), v = integer(), w = double(), stringsAsFactors = FALSE) + + # Edge case: empty graph + if (nrow(edges) == 0) { + cat("No edges in graph.\n") + return(invisible(list(edges = mst_edges, total_weight = 0))) + } + + while (num_trees > 1) { + cheapest <- rep(NA_integer_, V) + + # For every edge, check components and record cheapest edge for each component + for (i in seq_len(nrow(edges))) { + u <- edges$u[i] + v <- edges$v[i] + w <- edges$w[i] + set_u <- find_set(u) + set_v <- find_set(v) + + if (set_u == set_v) next + + if (is.na(cheapest[set_u]) || edges$w[cheapest[set_u]] > w) { + cheapest[set_u] <- i + } + if (is.na(cheapest[set_v]) || edges$w[cheapest[set_v]] > w) { + cheapest[set_v] <- i + } + } + + any_added <- FALSE + + # Add the cheapest edges to MST + for (node in seq_len(V)) { + idx <- cheapest[node] + if (is.na(idx)) next + + u <- edges$u[idx] + v <- edges$v[idx] + w <- edges$w[idx] + set_u <- find_set(u) + set_v <- find_set(v) + + if (set_u != set_v) { + if (union_set(set_u, set_v)) { + mst_weight <- mst_weight + w + mst_edges <- rbind(mst_edges, data.frame(u = u, v = v, w = w, stringsAsFactors = FALSE)) + num_trees <- num_trees - 1L + any_added <- TRUE + } + } + } + + # If no edges were added in this pass, the graph is disconnected + if (!any_added) { + cat("Graph appears disconnected; stopping. No spanning tree exists that connects all vertices.\n") + break + } + } + + cat("Edges in MST:\n") + if (nrow(mst_edges) > 0) { + for (i in seq_len(nrow(mst_edges))) { + cat(mst_edges$u[i], "--", mst_edges$v[i], "==", mst_edges$w[i], "\n") + } + } else { + cat("(none)\n") + } + cat("Total weight of MST:", mst_weight, "\n") + + invisible(list(edges = mst_edges, total_weight = mst_weight)) +} + +# Example usage and test +cat("=== Boruvka's MST Algorithm (improved) ===\n") +g <- create_graph(4) +g <- add_edge(g, 1, 2, 10) +g <- add_edge(g, 1, 3, 6) +g <- add_edge(g, 1, 4, 5) +g <- add_edge(g, 2, 4, 15) +g <- add_edge(g, 3, 4, 4) + +cat("Graph edges:\n") +print(g$edges) +cat("\nComputing MST...\n") +boruvka_mst(g) diff --git a/graph_algorithms/edmonds_karp.r b/graph_algorithms/edmonds_karp.r new file mode 100644 index 00000000..fdfddb72 --- /dev/null +++ b/graph_algorithms/edmonds_karp.r @@ -0,0 +1,96 @@ +# Edmonds-Karp Maximum Flow (Ford-Fulkerson with BFS) +# +# This R implementation follows the repository style: clear header, example +# usage, and 1-based vertex indexing. The algorithm computes maximum flow +# in a directed graph given by a capacity matrix. It returns the max flow and +# the residual capacity matrix. +# +# Time Complexity: O(V * E^2) in the naive implementation; Edmonds-Karp is O(V * E^2) +# Space Complexity: O(V^2) for the capacity/residual matrix + +# BFS to find an augmenting path. Returns list(found, parent) +bfs_ek <- function(capacity, source, sink) { + n <- nrow(capacity) + visited <- rep(FALSE, n) + parent <- rep(-1L, n) + + # Simple queue implementation using preallocated vector + queue <- integer(n) + front <- 1L + rear <- 1L + queue[rear] <- source + visited[source] <- TRUE + + while (front <= rear) { + u <- queue[front] + front <- front + 1L + for (v in seq_len(n)) { + if (!visited[v] && capacity[u, v] > 0) { + rear <- rear + 1L + queue[rear] <- v + visited[v] <- TRUE + parent[v] <- u + if (v == sink) { + return(list(found = TRUE, parent = parent)) + } + } + } + } + list(found = FALSE, parent = parent) +} + +# Edmonds-Karp main function +edmonds_karp <- function(capacity, source, sink) { + # Ensure capacity is a numeric matrix and uses 1-based indexing for vertices + cap <- as.matrix(capacity) + n <- nrow(cap) + if (source < 1 || source > n || sink < 1 || sink > n) stop("source/sink out of range") + + max_flow <- 0 + + repeat { + res <- bfs_ek(cap, source, sink) + if (!res$found) break + parent <- res$parent + + # Find minimum residual capacity along the path + path_flow <- Inf + s <- sink + while (s != source) { + u <- parent[s] + path_flow <- min(path_flow, cap[u, s]) + s <- u + } + + # Update residual capacities along the path + v <- sink + while (v != source) { + u <- parent[v] + cap[u, v] <- cap[u, v] - path_flow + cap[v, u] <- cap[v, u] + path_flow + v <- parent[v] + } + + max_flow <- max_flow + path_flow + } + + invisible(list(max_flow = max_flow, residual = cap)) +} + +# Example usage +cat("=== Edmonds-Karp Maximum Flow (R) ===\n") +# Graph represented as capacity matrix (6x6) +graph <- matrix(c( + 0, 16, 13, 0, 0, 0, + 0, 0, 10, 12, 0, 0, + 0, 4, 0, 0, 14, 0, + 0, 0, 9, 0, 0, 20, + 0, 0, 0, 7, 0, 4, + 0, 0, 0, 0, 0, 0 +), nrow = 6, byrow = TRUE) + +# Note: Python example used 0-based indices; this R example uses 1-based indices +source <- 1L +sink <- 6L +res <- edmonds_karp(graph, source, sink) +cat("Maximum Flow:", res$max_flow, "\n") diff --git a/graph_algorithms/hamiltonian_path.r b/graph_algorithms/hamiltonian_path.r new file mode 100644 index 00000000..2d3da356 --- /dev/null +++ b/graph_algorithms/hamiltonian_path.r @@ -0,0 +1,83 @@ +# Hamiltonian Path (Backtracking) +# +# This implementation searches for a Hamiltonian Path in an undirected graph +# represented by an adjacency matrix. It uses backtracking to try all possible +# vertex sequences. The implementation follows the style used in other +# algorithms in the `R/graph_algorithms` folder. +# +# Time Complexity: O(n!) in the worst case (backtracking over permutations) +# Space Complexity: O(n) for the path and recursion stack +# +# Input: adjacency matrix `graph` (n x n) +# Output: prints a Hamiltonian path if found and returns TRUE, otherwise prints +# a message and returns FALSE + +# Function to check if vertex v can be added to path at position pos +isSafe <- function(v, graph, path, pos) { + # Check adjacency between current vertex and previous vertex + if (graph[path[pos - 1], v] == 0) + return(FALSE) + + # Check if vertex is already in path + if (v %in% path) + return(FALSE) + + return(TRUE) +} + +# Recursive function to find Hamiltonian path +hamiltonianUtil <- function(graph, path, pos) { + n <- nrow(graph) + + # Base case: if all vertices are included in the path + if (pos > n) + return(TRUE) + + for (v in 1:n) { + if (isSafe(v, graph, path, pos)) { + path[pos] <- v + + if (hamiltonianUtil(graph, path, pos + 1)) + return(TRUE) + + # Backtrack + path[pos] <- -1 + } + } + return(FALSE) +} + +# Main function to find Hamiltonian path +hamiltonianPath <- function(graph) { + n <- nrow(graph) + + for (start in 1:n) { + path <- rep(-1, n) + path[1] <- start + + if (hamiltonianUtil(graph, path, 2)) { + cat("Hamiltonian Path found:\n") + print(path) + return(TRUE) + } + } + + cat("No Hamiltonian Path found.\n") + return(FALSE) +} + +# Example usage and test +cat("=== Hamiltonian Path (Backtracking) ===\n") + +graph <- matrix(c( + 0, 1, 1, 0, + 1, 0, 1, 1, + 1, 1, 0, 1, + 0, 1, 1, 0 +), nrow = 4, byrow = TRUE) + +cat("Adjacency matrix:\n") +print(graph) + +cat("\nSearching for Hamiltonian Path...\n") +hamiltonianPath(graph) diff --git a/graph_algorithms/push_relabel.r b/graph_algorithms/push_relabel.r new file mode 100644 index 00000000..41324eeb --- /dev/null +++ b/graph_algorithms/push_relabel.r @@ -0,0 +1,106 @@ +# Push-Relabel (Preflow-Push) Maximum Flow Algorithm +# +# This implementation follows the push-relabel method for computing maximum +# flow in a directed graph represented by a capacity matrix. It is translated +# from a Python class-based implementation into an R functional style while +# keeping the repository's conventions (clear header, example usage, 1-based indexing). +# +# Time Complexity: O(V^3) in the generic implementation; practical performance +# depends on heuristics (gap relabeling, global relabeling) which are not included here. +# Space Complexity: O(V^2) for flow and capacity matrices + +push_relabel <- function(capacity, source, sink) { + cap <- as.matrix(capacity) + n <- nrow(cap) + if (ncol(cap) != n) stop("capacity must be a square matrix") + if (source < 1 || source > n || sink < 1 || sink > n) stop("source/sink out of range") + + flow <- matrix(0, n, n) + excess <- rep(0, n) + height <- rep(0L, n) + + # Push operation + push <- function(u, v) { + send <- min(excess[u], cap[u, v] - flow[u, v]) + if (send <= 0) return(FALSE) + flow[u, v] <<- flow[u, v] + send + flow[v, u] <<- flow[v, u] - send + excess[u] <<- excess[u] - send + excess[v] <<- excess[v] + send + TRUE + } + + # Relabel operation + relabel <- function(u) { + min_height <- Inf + for (v in seq_len(n)) { + if (cap[u, v] - flow[u, v] > 0) { + min_height <- min(min_height, height[v]) + } + } + if (is.finite(min_height)) { + height[u] <<- as.integer(min_height + 1) + } + } + + # Discharge operation + discharge <- function(u) { + while (excess[u] > 0) { + pushed <- FALSE + for (v in seq_len(n)) { + if (cap[u, v] - flow[u, v] > 0 && height[u] == height[v] + 1) { + push(u, v) + pushed <- TRUE + if (excess[u] <= 0) break + } + } + if (!pushed) relabel(u) + } + } + + # Initialization: preflow from source + height[source] <- as.integer(n) + for (v in seq_len(n)) { + if (cap[source, v] > 0) { + flow[source, v] <- cap[source, v] + flow[v, source] <- -cap[source, v] + excess[v] <- cap[source, v] + excess[source] <- excess[source] - cap[source, v] + } + } + + # List of active vertices (exclude source and sink) + vertices <- setdiff(seq_len(n), c(source, sink)) + p <- 1L + while (p <= length(vertices)) { + u <- vertices[p] + old_height <- height[u] + discharge(u) + if (height[u] > old_height) { + # move to front + vertices <- c(u, vertices[-p]) + p <- 1L + } else { + p <- p + 1L + } + } + + maxflow <- sum(flow[source, ]) + invisible(list(max_flow = maxflow, flow = flow, excess = excess, height = height)) +} + +# Example usage +cat("=== Push-Relabel Maximum Flow (R) ===\n") +graph <- matrix(c( + 0, 16, 13, 0, 0, 0, + 0, 0, 10, 12, 0, 0, + 0, 4, 0, 0, 14, 0, + 0, 0, 9, 0, 0, 20, + 0, 0, 0, 7, 0, 4, + 0, 0, 0, 0, 0, 0 +), nrow = 6, byrow = TRUE) + +source <- 1L +sink <- 6L +res <- push_relabel(graph, source, sink) +cat("Maximum Flow:", res$max_flow, "\n") diff --git a/mathematics/fast_fourier_transform.r b/mathematics/fast_fourier_transform.r new file mode 100644 index 00000000..0ce03f96 --- /dev/null +++ b/mathematics/fast_fourier_transform.r @@ -0,0 +1,53 @@ +# Fast Fourier Transform (Cooley-Tukey recursive implementation) +# +# This implementation accepts a numeric or complex vector and returns +# its discrete Fourier transform as a complex vector. If the input length +# is not a power of two, the vector is zero-padded to the next power of two. +# +# Usage: +# source('mathematics/fast_fourier_transform.r') +# x <- c(0,1,2,3) +# fft_result <- fft_recursive(x) +# print(fft_result) + +next_power_of_two <- function(n) { + if (n <= 0) return(1) + p <- 1 + while (p < n) p <- p * 2 + p +} + +fft_recursive <- function(x) { + # Ensure input is complex + x <- as.complex(x) + N <- length(x) + + # Pad to next power of two if necessary + M <- next_power_of_two(N) + if (M != N) { + x <- c(x, rep(0+0i, M - N)) + N <- M + } + + if (N == 1) return(x) + + even <- fft_recursive(x[seq(1, N, by = 2)]) + odd <- fft_recursive(x[seq(2, N, by = 2)]) + + factor <- exp(-2i * pi * (0:(N/2 - 1)) / N) + T <- factor * odd + + c(even + T, even - T) +} + +# Example usage when run directly with Rscript +if (identical(Sys.getenv("R_SCRIPT_NAME"), "") && interactive()) { + # Running in interactive R session - show sample + x <- c(0, 1, 2, 3) + cat("Input:\n") + print(x) + cat("FFT result:\n") + print(fft_recursive(x)) +} + +# When running via Rscript, users can call: Rscript -e "source('R/mathematics/fast_fourier_transform.r'); print(fft_recursive(c(0,1,2,3)))"