Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions documentation/boruvka_mst.md
Original file line number Diff line number Diff line change
@@ -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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't add documentation separately from the code files, please remove it


## 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.
29 changes: 29 additions & 0 deletions documentation/fast_fourier_transform.md
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 27 additions & 0 deletions documentation/hamiltonian_path.md
Original file line number Diff line number Diff line change
@@ -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.
142 changes: 142 additions & 0 deletions graph_algorithms/boruvka_mst.r
Original file line number Diff line number Diff line change
@@ -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)
83 changes: 83 additions & 0 deletions graph_algorithms/hamiltonian_path.r
Original file line number Diff line number Diff line change
@@ -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)
53 changes: 53 additions & 0 deletions mathematics/fast_fourier_transform.r
Original file line number Diff line number Diff line change
@@ -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)))"