Skip to content

Commit 5d69d71

Browse files
committed
feat: Add topological sort and disjoint set algorithms
1 parent 1887b93 commit 5d69d71

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed

graph_algorithms/disjoint_set.r

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Disjoint Set Union (Union-Find)
2+
#
3+
# A data structure that keeps track of elements partitioned into disjoint sets.
4+
# Supports union and find operations efficiently using path compression and union by rank.
5+
#
6+
# Time Complexity: Nearly O(1) per operation (amortized with path compression)
7+
# Space Complexity: O(N)
8+
#
9+
# Applications:
10+
# - Detecting cycles in undirected graphs
11+
# - Kruskal's MST algorithm
12+
# - Network connectivity
13+
# - Social network grouping
14+
# - Image segmentation
15+
# - Connected components in graphs
16+
17+
make_set <- function(n) {
18+
#' Initialize disjoint set with n elements
19+
#' @param n: Number of elements (0 to n-1)
20+
#' @return: List containing parent array and rank array
21+
22+
# Each element is initially its own parent
23+
parent <- 0:(n-1)
24+
# Initial rank (tree height) is 0 for all elements
25+
rank <- rep(0, n)
26+
27+
list(parent = parent, rank = rank)
28+
}
29+
30+
find_set <- function(ds, v) {
31+
#' Find the representative (root) of the set containing v
32+
#' Uses path compression for efficiency
33+
#' @param ds: Disjoint set data structure
34+
#' @param v: Element to find
35+
#' @return: Representative of v's set
36+
37+
if (ds$parent[v + 1] != v) {
38+
# Path compression: Make all nodes on path point to root
39+
ds$parent[v + 1] <- find_set(ds, ds$parent[v + 1])
40+
}
41+
ds$parent[v + 1]
42+
}
43+
44+
union_sets <- function(ds, a, b) {
45+
#' Union two sets by rank
46+
#' @param ds: Disjoint set data structure
47+
#' @param a: First element
48+
#' @param b: Second element
49+
#' @return: Updated disjoint set structure
50+
51+
a_root <- find_set(ds, a)
52+
b_root <- find_set(ds, b)
53+
54+
if (a_root != b_root) {
55+
# Union by rank: Attach smaller rank tree under root of higher rank tree
56+
if (ds$rank[a_root + 1] < ds$rank[b_root + 1]) {
57+
# Swap to ensure a_root has higher rank
58+
temp <- a_root
59+
a_root <- b_root
60+
b_root <- temp
61+
}
62+
63+
ds$parent[b_root + 1] <- a_root
64+
65+
# If ranks are equal, increment the rank of the root
66+
if (ds$rank[a_root + 1] == ds$rank[b_root + 1]) {
67+
ds$rank[a_root + 1] <- ds$rank[a_root + 1] + 1
68+
}
69+
}
70+
71+
return(ds)
72+
}
73+
74+
# Example Usage
75+
# Test case 1: Basic operations
76+
cat("=== Test Case 1: Basic Union-Find Operations ===\n")
77+
ds1 <- make_set(5) # Create sets for elements 0-4
78+
cat("Initial parents:", paste(ds1$parent, collapse = " "), "\n")
79+
80+
ds1 <- union_sets(ds1, 0, 1) # Union first two elements
81+
ds1 <- union_sets(ds1, 2, 3) # Union next two elements
82+
cat("After unions:", paste(ds1$parent, collapse = " "), "\n")
83+
84+
cat("Find(1):", find_set(ds1, 1), "\n")
85+
cat("Find(2):", find_set(ds1, 2), "\n")
86+
87+
# Test case 2: Path compression
88+
cat("\n=== Test Case 2: Path Compression ===\n")
89+
ds2 <- make_set(6)
90+
ds2 <- union_sets(ds2, 0, 1)
91+
ds2 <- union_sets(ds2, 1, 2)
92+
ds2 <- union_sets(ds2, 2, 3)
93+
cat("Parents before find:", paste(ds2$parent, collapse = " "), "\n")
94+
find_set(ds2, 3) # This should compress the path
95+
cat("Parents after find:", paste(ds2$parent, collapse = " "), "\n")
96+
97+
# Test case 3: Connected components
98+
cat("\n=== Test Case 3: Finding Connected Components ===\n")
99+
ds3 <- make_set(7)
100+
edges <- list(c(0,1), c(1,2), c(3,4), c(5,6))
101+
for (edge in edges) {
102+
ds3 <- union_sets(ds3, edge[1], edge[2])
103+
}
104+
105+
# Print connected components
106+
components <- list()
107+
for (i in 0:6) {
108+
root <- find_set(ds3, i)
109+
if (is.null(components[[as.character(root)]])) {
110+
components[[as.character(root)]] <- c()
111+
}
112+
components[[as.character(root)]] <- c(components[[as.character(root)]], i)
113+
}
114+
115+
cat("Connected Components:\n")
116+
for (comp in components) {
117+
cat(" Group:", paste(comp, collapse = " "), "\n")
118+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Topological Sort (Kahn's Algorithm)
2+
#
3+
# The topological sort of a directed acyclic graph (DAG) is a linear ordering
4+
# of vertices such that for every directed edge u -> v, vertex u comes before v.
5+
#
6+
# Time Complexity: O(V + E)
7+
# Space Complexity: O(V)
8+
#
9+
# Applications:
10+
# - Task scheduling and dependency resolution
11+
# - Build systems (compilation order)
12+
# - Job scheduling in distributed systems
13+
# - Resolving package dependencies
14+
15+
topological_sort <- function(V, edges) {
16+
#' Perform topological sorting on a DAG
17+
#' @param V: Number of vertices (0 to V-1)
18+
#' @param edges: List of edge pairs (u, v)
19+
#' @return: Topologically sorted order or NULL if cycle exists
20+
21+
adj <- vector("list", V)
22+
indegree <- rep(0, V)
23+
24+
# Build adjacency list and calculate in-degrees
25+
for (e in edges) {
26+
u <- e[1]
27+
v <- e[2]
28+
adj[[u]] <- c(adj[[u]], v)
29+
indegree[v] <- indegree[v] + 1
30+
}
31+
32+
# Initialize queue with vertices having no incoming edges
33+
q <- which(indegree == 0)
34+
topo <- c()
35+
36+
# Process vertices in topological order
37+
while (length(q) > 0) {
38+
node <- q[1]
39+
q <- q[-1]
40+
topo <- c(topo, node)
41+
42+
# Update in-degrees of neighbors
43+
for (nbr in adj[[node]]) {
44+
indegree[nbr] <- indegree[nbr] - 1
45+
if (indegree[nbr] == 0) {
46+
q <- c(q, nbr) # Add to queue when in-degree becomes 0
47+
}
48+
}
49+
}
50+
51+
# Check if valid topological sort exists
52+
if (length(topo) == V) {
53+
return(topo)
54+
} else {
55+
return(NULL) # Cycle detected
56+
}
57+
}
58+
59+
# Example Usage
60+
# Test case 1: Simple DAG
61+
cat("=== Test Case 1: Simple DAG ===\n")
62+
V1 <- 4
63+
edges1 <- list(c(0,1), c(0,2), c(2,3), c(1,3))
64+
result1 <- topological_sort(V1, edges1)
65+
if (!is.null(result1)) {
66+
cat("Topological Order:", paste(result1, collapse = " -> "), "\n")
67+
} else {
68+
cat("Graph contains a cycle!\n")
69+
}
70+
71+
# Test case 2: Larger DAG
72+
cat("\n=== Test Case 2: Larger DAG ===\n")
73+
V2 <- 6
74+
edges2 <- list(c(5,2), c(5,0), c(4,0), c(4,1), c(2,3), c(3,1))
75+
result2 <- topological_sort(V2, edges2)
76+
if (!is.null(result2)) {
77+
cat("Topological Order:", paste(result2, collapse = " -> "), "\n")
78+
} else {
79+
cat("Graph contains a cycle!\n")
80+
}
81+
82+
# Test case 3: Graph with cycle
83+
cat("\n=== Test Case 3: Graph with Cycle ===\n")
84+
V3 <- 3
85+
edges3 <- list(c(0,1), c(1,2), c(2,0)) # Cycle: 0->1->2->0
86+
result3 <- topological_sort(V3, edges3)
87+
if (!is.null(result3)) {
88+
cat("Topological Order:", paste(result3, collapse = " -> "), "\n")
89+
} else {
90+
cat("Graph contains a cycle!\n")
91+
}

0 commit comments

Comments
 (0)