Skip to content

Commit

Permalink
refactor undirected graph implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
aradwann committed Nov 1, 2024
1 parent f7b664f commit 1b0b0dc
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 115 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
# clrs

![Rust workflow](https://github.com/aradwann/algorithms-illuminated/actions/workflows/rust.yml/badge.svg)

my notes and implementation (in rust) of some algorithms in:

[Algorithms Illuminated](https://www.algorithmsilluminated.org/)
[Algorithms Illuminated](https://www.algorithmsilluminated.org/)

## Sort

### Sort
1. [merge sort](./src/sort/merge.rs)
2. [heap sort](./src/sort/heap.rs)

### Graph
## Graph

graphs are represented with adjancency list, currently (with two usize Vecs in Rust)
> at the moment: parallel edges aren't allowed
> at the moment: parallel edges aren't allowed
> at the moment: self-loops aren't allowed
> at the moment: self-loops aren't allowed
1. [undirected graph](./src/graph/undirected_graph.rs)
1. [breadth-first search BFS](./src/graph/undirected_graph.rs#L89-L106)
2. [undirected connected componenets UCC](./src/graph/undirected_graph.rs#L129-L154)
2. [directed graph](./src/graph/directed_graph.rs)
2. [directed graph](./src/graph/directed_graph.rs)
1. depth-first search DFS ([iterative](./src/graph/directed_graph.rs#L88-L106) & [recursive](./src/graph/directed_graph.rs#L117-L129))
2. [Topo Sort](./src/graph/directed_graph.rs#L143-L153)
2. [Topo Sort](./src/graph/directed_graph.rs#L143-L153)


TODO:

- [] track memory footprint
219 changes: 111 additions & 108 deletions src/graph/undirected_graph.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::VecDeque;
use std::collections::{HashSet, VecDeque};

/// representing a graph using an adjacency list which is
/// 1) An array containing the graph vertices
Expand All @@ -8,70 +8,48 @@ use std::collections::VecDeque;

type VertexIndex = usize;

/// Represents data associated with each vertex
#[derive(Debug, Clone)]
struct VertexData {
edges: Vec<EdgeIndex>,
value: char,
explored: bool,
cc_value: usize,
neighbors: HashSet<VertexIndex>,
_value: char,
cc_value: Option<usize>,
}

type EdgeIndex = usize;

#[derive(Debug)]
struct EdgeData(VertexIndex, VertexIndex);
impl EdgeData {
fn new(tail: VertexIndex, head: VertexIndex) -> Self {
if tail == head {
panic!("self-loops aren't allowed atm")
}
// order of vertices does not matter as it is undirected
// so i order it that it begins always with the smaller
if tail > head {
Self(head, tail)
} else {
Self(tail, head)
impl VertexData {
fn new(value: char) -> Self {
VertexData {
neighbors: HashSet::new(),
_value: value,
cc_value: None,
}
}
}

#[derive(Debug)]
pub struct UndirectedGraph {
vertices: Vec<VertexData>,
edges: Vec<EdgeData>,
}

impl UndirectedGraph {
pub fn new() -> Self {
UndirectedGraph {
vertices: vec![],
edges: vec![],
}
UndirectedGraph { vertices: vec![] }
}
pub fn add_vertex(&mut self, value: char) -> VertexIndex {
let index = self.vertices.len();
self.vertices.push(VertexData {
edges: vec![],
value,
explored: false,
cc_value: 0, // 0 means no cc value yet
});
self.vertices.push(VertexData::new(value));
index
}
pub fn add_edge(&mut self, tail: VertexIndex, head: VertexIndex) -> EdgeIndex {
for e in &self.edges {
if e.0 == tail && e.1 == head {
panic!("parallel edges aren't allowed atm")
}
pub fn add_edge(&mut self, v1: VertexIndex, v2: VertexIndex) -> Result<(), &str> {
if v1 == v2 {
return Err("Self-loops are not allowed");
}
if self.vertices[v1].neighbors.contains(&v2) {
return Err("Parallel edges are not allowed");
}
let edge_index = self.edges.len();
self.edges.push(EdgeData::new(tail, head));

let vertex_data_1 = &mut self.vertices[tail];
vertex_data_1.edges.push(edge_index);

let vertex_data_2 = &mut self.vertices[head];
vertex_data_2.edges.push(edge_index);
edge_index
self.vertices[v1].neighbors.insert(v2);
self.vertices[v2].neighbors.insert(v1);
Ok(())
}

/// Pseudocode
Expand All @@ -86,27 +64,25 @@ impl UndirectedGraph {
/// if w is unexplored then
/// mark w as explored
/// add w to the end of Q
pub fn bfs(&mut self, _value: char) {
self.mark_all_vertices_unexplored();
pub fn bfs(&mut self, start_vertex: VertexIndex) {
self.clear_exploration();

let mut queue = VecDeque::new();
queue.push_back(self.edges[0].0); // push the first vertex data into the queue
while !queue.is_empty() {
let vertex_index = queue.pop_front().unwrap(); // the index of the vertex
let vertex_data = self.vertices[vertex_index].clone();
for edge_index in vertex_data.edges {
let EdgeData(_, vertex_index_2) = self.edges[edge_index];
let vertex_2 = &mut self.vertices[vertex_index_2];
if !vertex_2.explored {
vertex_2.explored = true;
println!("exploring Vertex {:#?}", &vertex_2);
queue.push_back(vertex_index_2);
queue.push_back(start_vertex);
self.vertices[start_vertex].cc_value = Some(1);

while let Some(v_index) = queue.pop_front() {
// Clone neighbors to avoid borrowing issues
let neighbors = self.vertices[v_index].neighbors.clone();
for neighbor_index in neighbors {
if self.vertices[neighbor_index].cc_value.is_none() {
self.vertices[neighbor_index].cc_value = Some(1);
println!("Exploring Vertex {:?}", self.vertices[neighbor_index]);
queue.push_back(neighbor_index);
}
}
}
}
fn mark_all_vertices_unexplored(&mut self) {
self.vertices.iter_mut().for_each(|n| n.explored = false);
}

/// Pseudocode
/// Input: undirected graph G=(V,E) in adjancency list representation with V = {1,2,3,4,...,n}
Expand All @@ -127,25 +103,24 @@ impl UndirectedGraph {
/// mark w as explored
/// add w to the end of Q
pub fn ucc(&mut self) {
self.mark_all_vertices_unexplored();
let mut num_cc = 0;
self.clear_exploration();
let mut component_count = 0;

for i in 0..self.vertices.len() {
if !self.vertices[i].explored {
num_cc += 1;
if self.vertices[i].cc_value.is_none() {
component_count += 1;
let mut queue = VecDeque::new();
queue.push_back(i); // push the first vertex index into the queue
while !queue.is_empty() {
let vertex_index = queue.pop_front().unwrap(); // the index of the vertex
let vertex_data = self.vertices[vertex_index].clone();

self.vertices[vertex_index].cc_value = num_cc;
for edge_index in vertex_data.edges {
let EdgeData(_, vertex_index_2) = self.edges[edge_index];
let vertex_2 = &mut self.vertices[vertex_index_2];
if !vertex_2.explored {
vertex_2.explored = true;
println!("exploring vertex {:#?}", &vertex_2);
queue.push_back(vertex_index_2);
queue.push_back(i);
self.vertices[i].cc_value = Some(component_count);

while let Some(v_index) = queue.pop_front() {
// Clone neighbors to avoid borrowing issues
let neighbors = self.vertices[v_index].neighbors.clone();
for neighbor_index in neighbors {
if self.vertices[neighbor_index].cc_value.is_none() {
self.vertices[neighbor_index].cc_value = Some(component_count);
println!("Exploring vertex {:?}", self.vertices[neighbor_index]);
queue.push_back(neighbor_index);
}
}
}
Expand All @@ -168,6 +143,10 @@ impl UndirectedGraph {
pub fn dfs_iterative() {
unimplemented!()
}

fn clear_exploration(&mut self) {
self.vertices.iter_mut().for_each(|v| v.cc_value = None);
}
}

impl Default for UndirectedGraph {
Expand All @@ -181,46 +160,70 @@ mod tests {
use super::*;

#[test]
#[should_panic(expected = "self-loops aren't allowed atm")]
fn test_create_self_loop() {
EdgeData::new(2, 2);
fn test_no_self_loops() {
let mut graph = UndirectedGraph::new();
let v = graph.add_vertex('A');
assert!(graph.add_edge(v, v).is_err());
}

#[test]
#[should_panic(expected = "parallel edges aren't allowed atm")]
fn test_add_parallel_edge() {
fn test_no_parallel_edges() {
let mut graph = UndirectedGraph::new();

graph.add_vertex('s');
graph.add_vertex('a');

graph.add_edge(0, 1);
graph.add_edge(0, 1);
let v1 = graph.add_vertex('A');
let v2 = graph.add_vertex('B');
assert!(graph.add_edge(v1, v2).is_ok());
assert!(graph.add_edge(v1, v2).is_err());
}

#[test]
fn test_create_undirected_graph() {
let mut graph = UndirectedGraph::new();

graph.add_vertex('s');
graph.add_vertex('a');
graph.add_vertex('b');
graph.add_vertex('c');
graph.add_vertex('d');
graph.add_vertex('e');

graph.add_edge(0, 1);
graph.add_edge(0, 2);
graph.add_edge(1, 3);
graph.add_edge(2, 3);
graph.add_edge(2, 4);
graph.add_edge(3, 4);
graph.add_edge(3, 5);
graph.add_edge(4, 5);

// assert graph has 8 vertices
assert_eq!(graph.edges.len(), 8);
// assert graph has 6 edges
// Adding vertices
let v_s = graph.add_vertex('s');
let v_a = graph.add_vertex('a');
let v_b = graph.add_vertex('b');
let v_c = graph.add_vertex('c');
let v_d = graph.add_vertex('d');
let v_e = graph.add_vertex('e');

// Adding edges
graph.add_edge(v_s, v_a).unwrap();
graph.add_edge(v_s, v_b).unwrap();
graph.add_edge(v_a, v_c).unwrap();
graph.add_edge(v_b, v_c).unwrap();
graph.add_edge(v_b, v_d).unwrap();
graph.add_edge(v_c, v_d).unwrap();
graph.add_edge(v_c, v_e).unwrap();
graph.add_edge(v_d, v_e).unwrap();

// Assert vertices count
assert_eq!(graph.vertices.len(), 6);

// Assert adjacency relationships
assert!(graph.vertices[v_s].neighbors.contains(&v_a));
assert!(graph.vertices[v_s].neighbors.contains(&v_b));
assert!(graph.vertices[v_a].neighbors.contains(&v_c));
assert!(graph.vertices[v_b].neighbors.contains(&v_c));
assert!(graph.vertices[v_b].neighbors.contains(&v_d));
assert!(graph.vertices[v_c].neighbors.contains(&v_d));
assert!(graph.vertices[v_c].neighbors.contains(&v_e));
assert!(graph.vertices[v_d].neighbors.contains(&v_e));

// Test BFS from 's'
graph.bfs(v_s);
for vertex in &graph.vertices {
assert_eq!(vertex.cc_value, Some(1)); // All vertices should be reachable from 's'
}

// Reset graph and test UCC (connected components)
graph.clear_exploration();
graph.ucc();

// All vertices should belong to the same connected component
let cc_value = graph.vertices[v_s].cc_value.unwrap();
for vertex in &graph.vertices {
assert_eq!(vertex.cc_value, Some(cc_value));
}
}
}

0 comments on commit 1b0b0dc

Please sign in to comment.