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/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/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/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)))"