From 4ec42aec68b3577226b20c7c877a25c2b98f2efb Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Thu, 3 Oct 2024 14:15:03 +0700 Subject: [PATCH 1/4] ref: refactor Eulerian Path --- src/graph/eulerian_path.rs | 374 ++++++++++++++++++++++++++----------- src/graph/mod.rs | 2 +- 2 files changed, 263 insertions(+), 113 deletions(-) diff --git a/src/graph/eulerian_path.rs b/src/graph/eulerian_path.rs index 2dcfa8b22a5..9f8a975fe9b 100644 --- a/src/graph/eulerian_path.rs +++ b/src/graph/eulerian_path.rs @@ -1,18 +1,43 @@ +//! This module provides functionality to find an Eulerian path in a directed graph. +//! An Eulerian path visits every edge exactly once. The algorithm checks if an Eulerian +//! path exists and, if so, constructs and returns the path. + use std::collections::LinkedList; use std::vec::Vec; -/// Struct representing an Eulerian Path in a directed graph. +/// Finds the Eulerian path in a directed graph represented by the number of nodes and edges. +/// +/// # Arguments +/// +/// * `nodes` - The number of nodes in the graph. +/// * `edges` - A vector of tuples representing the directed edges in the graph, where each tuple +/// is of the form `(u, v)` indicating a directed edge from `u` to `v`. +/// +/// # Returns +/// +/// An `Option` containing a vector representing the Eulerian path if it exists, or `None` if no such path exists. +pub fn find_eulerian_path(nodes: usize, edges: Vec<(usize, usize)>) -> Option> { + let mut graph = vec![Vec::new(); nodes]; + for (u, v) in edges { + graph[u].push(v); + } + + let mut solver = EulerianPath::new(graph); + solver.find_eulerian_path() +} + +/// Struct representing an Eulerian path in a directed graph. pub struct EulerianPath { - n: usize, // Number of nodes in the graph - edge_count: usize, // Total number of edges in the graph - in_degree: Vec, // In-degrees of nodes - out_degree: Vec, // Out-degrees of nodes - path: LinkedList, // Linked list to store the Eulerian path - graph: Vec>, // Adjacency list representing the directed graph + nodes: usize, // Number of nodes + edges: usize, // Total number of edges + in_deg: Vec, // In-degrees of nodes + out_deg: Vec, // Out-degrees of nodes + path: LinkedList, // Stores the Eulerian path + graph: Vec>, // Adjacency list } impl EulerianPath { - /// Creates a new instance of EulerianPath. + /// Creates a new instance of `EulerianPath` for the given graph. /// /// # Arguments /// @@ -20,39 +45,38 @@ impl EulerianPath { /// /// # Returns /// - /// A new EulerianPath instance. + /// A new `EulerianPath` instance. pub fn new(graph: Vec>) -> Self { - let n = graph.len(); Self { - n, - edge_count: 0, - in_degree: vec![0; n], - out_degree: vec![0; n], + nodes: graph.len(), + edges: 0, + in_deg: vec![0; graph.len()], + out_deg: vec![0; graph.len()], path: LinkedList::new(), graph, } } - /// Finds an Eulerian path in the directed graph. + /// Finds an Eulerian path if it exists. /// /// # Returns /// - /// An `Option` containing the Eulerian path if it exists, or `None` if no Eulerian path exists. - pub fn find_eulerian_path(&mut self) -> Option> { - self.initialize(); + /// An `Option` containing the Eulerian path as a vector if it exists, or `None` otherwise. + fn find_eulerian_path(&mut self) -> Option> { + self.init_degrees(); if !self.has_eulerian_path() { return None; } - let start_node = self.find_start_node(); - self.traverse(start_node); + let start = self.find_start(); + self.dfs(start); - if self.path.len() != self.edge_count + 1 { + if self.path.len() != self.edges + 1 { return None; } - let mut solution = Vec::with_capacity(self.edge_count + 1); + let mut solution = Vec::with_capacity(self.edges + 1); while let Some(node) = self.path.pop_front() { solution.push(node); } @@ -60,13 +84,13 @@ impl EulerianPath { Some(solution) } - /// Initializes the degree vectors and counts the total number of edges in the graph. - fn initialize(&mut self) { - for (from, neighbors) in self.graph.iter().enumerate() { - for &to in neighbors { - self.in_degree[to] += 1; - self.out_degree[from] += 1; - self.edge_count += 1; + /// Initializes in-degrees, out-degrees, and counts the total number of edges. + fn init_degrees(&mut self) { + for (u, neighbors) in self.graph.iter().enumerate() { + for &v in neighbors { + self.in_deg[v] += 1; + self.out_deg[u] += 1; + self.edges += 1; } } } @@ -77,57 +101,50 @@ impl EulerianPath { /// /// `true` if an Eulerian path exists, `false` otherwise. fn has_eulerian_path(&self) -> bool { - if self.edge_count == 0 { + if self.edges == 0 { return false; } - let (mut start_nodes, mut end_nodes) = (0, 0); - for i in 0..self.n { - let in_degree = self.in_degree[i] as i32; - let out_degree = self.out_degree[i] as i32; - - if (out_degree - in_degree) > 1 || (in_degree - out_degree) > 1 { - return false; - } else if (out_degree - in_degree) == 1 { - start_nodes += 1; - } else if (in_degree - out_degree) == 1 { - end_nodes += 1; + let (mut start, mut end) = (0, 0); + for i in 0..self.nodes { + let (in_deg, out_deg) = (self.in_deg[i] as isize, self.out_deg[i] as isize); + match out_deg - in_deg { + 1 => start += 1, + -1 => end += 1, + d if d.abs() > 1 => return false, + _ => (), } } - (end_nodes == 0 && start_nodes == 0) || (end_nodes == 1 && start_nodes == 1) + (start == 0 && end == 0) || (start == 1 && end == 1) } - /// Finds the starting node for the Eulerian path. + /// Finds the start node for the Eulerian path. /// /// # Returns /// - /// The index of the starting node. - fn find_start_node(&self) -> usize { - let mut start = 0; - for i in 0..self.n { - if self.out_degree[i] - self.in_degree[i] == 1 { + /// The index of the start node. + fn find_start(&self) -> usize { + for i in 0..self.nodes { + if self.out_deg[i] > self.in_deg[i] { return i; } - if self.out_degree[i] > 0 { - start = i; - } } - start + (0..self.nodes).find(|&i| self.out_deg[i] > 0).unwrap_or(0) } - /// Traverses the graph to find the Eulerian path recursively. + /// Depth-first search traversal to construct the Eulerian path. /// /// # Arguments /// - /// * `at` - The current node being traversed. - fn traverse(&mut self, at: usize) { - while self.out_degree[at] != 0 { - let next = self.graph[at][self.out_degree[at] - 1]; - self.out_degree[at] -= 1; - self.traverse(next); + /// * `u` - The current node being traversed. + fn dfs(&mut self, u: usize) { + while self.out_deg[u] > 0 { + let v = self.graph[u][self.out_deg[u] - 1]; + self.out_deg[u] -= 1; + self.dfs(v); } - self.path.push_front(at); + self.path.push_front(u); } } @@ -135,59 +152,192 @@ impl EulerianPath { mod tests { use super::*; - /// Creates an empty graph with `n` nodes. - fn create_empty_graph(n: usize) -> Vec> { - vec![Vec::new(); n] - } - - /// Adds a directed edge from `from` to `to` in the graph. - fn add_directed_edge(graph: &mut [Vec], from: usize, to: usize) { - graph[from].push(to); - } - - #[test] - fn good_path_test() { - let n = 7; - let mut graph = create_empty_graph(n); - - add_directed_edge(&mut graph, 1, 2); - add_directed_edge(&mut graph, 1, 3); - add_directed_edge(&mut graph, 2, 2); - add_directed_edge(&mut graph, 2, 4); - add_directed_edge(&mut graph, 2, 4); - add_directed_edge(&mut graph, 3, 1); - add_directed_edge(&mut graph, 3, 2); - add_directed_edge(&mut graph, 3, 5); - add_directed_edge(&mut graph, 4, 3); - add_directed_edge(&mut graph, 4, 6); - add_directed_edge(&mut graph, 5, 6); - add_directed_edge(&mut graph, 6, 3); - - let mut solver = EulerianPath::new(graph); - - assert_eq!( - solver.find_eulerian_path().unwrap(), - vec![1, 3, 5, 6, 3, 2, 4, 3, 1, 2, 2, 4, 6] - ); + macro_rules! test_cases { + ($($name:ident: $test_case:expr,)*) => { + $( + #[test] + fn $name() { + let (n, edges, expected) = $test_case; + assert_eq!(find_eulerian_path(n, edges), expected); + } + )* + } } - #[test] - fn small_path_test() { - let n = 5; - let mut graph = create_empty_graph(n); - - add_directed_edge(&mut graph, 0, 1); - add_directed_edge(&mut graph, 1, 2); - add_directed_edge(&mut graph, 1, 4); - add_directed_edge(&mut graph, 1, 3); - add_directed_edge(&mut graph, 2, 1); - add_directed_edge(&mut graph, 4, 1); - - let mut solver = EulerianPath::new(graph); - - assert_eq!( - solver.find_eulerian_path().unwrap(), - vec![0, 1, 4, 1, 2, 1, 3] - ); + test_cases! { + test_eulerian_cycle: ( + 7, + vec![ + (1, 2), + (1, 3), + (2, 2), + (2, 4), + (2, 4), + (3, 1), + (3, 2), + (3, 5), + (4, 3), + (4, 6), + (5, 6), + (6, 3) + ], + Some(vec![1, 3, 5, 6, 3, 2, 4, 3, 1, 2, 2, 4, 6]) + ), + test_simple_path: ( + 5, + vec![ + (0, 1), + (1, 2), + (1, 4), + (1, 3), + (2, 1), + (4, 1) + ], + Some(vec![0, 1, 4, 1, 2, 1, 3]) + ), + test_disconnected_graph: ( + 4, + vec![ + (0, 1), + (2, 3) + ], + None::> + ), + test_single_cycle: ( + 4, + vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 0) + ], + Some(vec![0, 1, 2, 3, 0]) + ), + test_empty_graph: ( + 3, + vec![], + None::> + ), + test_unbalanced_path: ( + 3, + vec![ + (0, 1), + (1, 2), + (2, 0), + (0, 2) + ], + Some(vec![0, 2, 0, 1, 2]) + ), + test_no_eulerian_path: ( + 3, + vec![ + (0, 1), + (0, 2) + ], + None::> + ), + test_complex_eulerian_path: ( + 6, + vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + (4, 0), + (0, 5), + (5, 0), + (2, 0) + ], + Some(vec![2, 0, 5, 0, 1, 2, 3, 4, 0]) + ), + test_single_node_self_loop: ( + 1, + vec![(0, 0)], + Some(vec![0, 0]) + ), + test_complete_graph: ( + 3, + vec![ + (0, 1), + (0, 2), + (1, 0), + (1, 2), + (2, 0), + (2, 1) + ], + Some(vec![0, 2, 1, 2, 0, 1, 0]) + ), + test_multiple_disconnected_components: ( + 6, + vec![ + (0, 1), + (2, 3), + (4, 5) + ], + None::> + ), + test_unbalanced_graph_with_path: ( + 4, + vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 1) + ], + Some(vec![0, 1, 2, 3, 1]) + ), + test_node_with_no_edges: ( + 4, + vec![ + (0, 1), + (1, 2) + ], + Some(vec![0, 1, 2]) + ), + test_multiple_edges_between_same_nodes: ( + 3, + vec![ + (0, 1), + (1, 2), + (1, 2), + (2, 0) + ], + Some(vec![1, 2, 0, 1, 2]) + ), + test_larger_graph_with_eulerian_path: ( + 10, + vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + (4, 5), + (5, 6), + (6, 7), + (7, 8), + (8, 9), + (9, 0), + (1, 6), + (6, 3), + (3, 8) + ], + Some(vec![1, 6, 3, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8]) + ), + test_no_edges_multiple_nodes: ( + 5, + vec![], + None::> + ), + test_multiple_start_and_end_nodes: ( + 4, + vec![ + (0, 1), + (1, 2), + (2, 0), + (0, 2), + (1, 3) + ], + None::> + ), } } diff --git a/src/graph/mod.rs b/src/graph/mod.rs index fb33cb3c3eb..d4b0b0d00cb 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -38,7 +38,7 @@ pub use self::detect_cycle::DetectCycle; pub use self::dijkstra::dijkstra; pub use self::dinic_maxflow::DinicMaxFlow; pub use self::disjoint_set_union::DisjointSetUnion; -pub use self::eulerian_path::EulerianPath; +pub use self::eulerian_path::find_eulerian_path; pub use self::floyd_warshall::floyd_warshall; pub use self::ford_fulkerson::ford_fulkerson; pub use self::graph_enumeration::enumerate_graph; From 6a6724ae3676c9e50353cf1abee9d33abcce70c8 Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Wed, 9 Oct 2024 20:11:09 +0700 Subject: [PATCH 2/4] feat(tests): add some edge tests --- src/graph/eulerian_path.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/graph/eulerian_path.rs b/src/graph/eulerian_path.rs index 9f8a975fe9b..c45ed787f89 100644 --- a/src/graph/eulerian_path.rs +++ b/src/graph/eulerian_path.rs @@ -13,6 +13,8 @@ use std::vec::Vec; /// * `edges` - A vector of tuples representing the directed edges in the graph, where each tuple /// is of the form `(u, v)` indicating a directed edge from `u` to `v`. /// +/// The function checks if an Eulerian path exists and, if so, constructs and returns one valid path. +/// /// # Returns /// /// An `Option` containing a vector representing the Eulerian path if it exists, or `None` if no such path exists. @@ -339,5 +341,39 @@ mod tests { ], None::> ), + test_single_edge: ( + 2, + vec![(0, 1)], + Some(vec![0, 1]) + ), + test_multiple_eulerian_paths: ( + 4, + vec![ + (0, 1), + (1, 2), + (2, 0), + (0, 3), + (3, 0) + ], + Some(vec![0, 3, 0, 1, 2, 0]) + ), + test_dag_path: ( + 4, + vec![ + (0, 1), + (1, 2), + (2, 3) + ], + Some(vec![0, 1, 2, 3]) + ), + test_parallel_edges_case: ( + 2, + vec![ + (0, 1), + (0, 1), + (1, 0) + ], + Some(vec![0, 1, 0, 1]) + ), } } From 2a8227e62c06038fa68859cbc124a631c4b2b02f Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Thu, 10 Oct 2024 21:03:36 +0700 Subject: [PATCH 3/4] chore: improve naming --- src/graph/eulerian_path.rs | 152 ++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/src/graph/eulerian_path.rs b/src/graph/eulerian_path.rs index c45ed787f89..6c3f400461b 100644 --- a/src/graph/eulerian_path.rs +++ b/src/graph/eulerian_path.rs @@ -3,150 +3,150 @@ //! path exists and, if so, constructs and returns the path. use std::collections::LinkedList; -use std::vec::Vec; -/// Finds the Eulerian path in a directed graph represented by the number of nodes and edges. +/// Finds an Eulerian path in a directed graph. /// /// # Arguments /// -/// * `nodes` - The number of nodes in the graph. -/// * `edges` - A vector of tuples representing the directed edges in the graph, where each tuple -/// is of the form `(u, v)` indicating a directed edge from `u` to `v`. -/// -/// The function checks if an Eulerian path exists and, if so, constructs and returns one valid path. +/// * `node_count` - The number of nodes in the graph. +/// * `edge_list` - A vector of tuples representing directed edges, where each tuple is of the form `(start, end)`. /// /// # Returns /// -/// An `Option` containing a vector representing the Eulerian path if it exists, or `None` if no such path exists. -pub fn find_eulerian_path(nodes: usize, edges: Vec<(usize, usize)>) -> Option> { - let mut graph = vec![Vec::new(); nodes]; - for (u, v) in edges { - graph[u].push(v); +/// An `Option>` containing the Eulerian path if it exists; otherwise, `None`. +pub fn find_eulerian_path(node_count: usize, edge_list: Vec<(usize, usize)>) -> Option> { + let mut adjacency_list = vec![Vec::new(); node_count]; + for (start, end) in edge_list { + adjacency_list[start].push(end); } - let mut solver = EulerianPath::new(graph); - solver.find_eulerian_path() + let mut eulerian_solver = EulerianPathSolver::new(adjacency_list); + eulerian_solver.find_path() } -/// Struct representing an Eulerian path in a directed graph. -pub struct EulerianPath { - nodes: usize, // Number of nodes - edges: usize, // Total number of edges - in_deg: Vec, // In-degrees of nodes - out_deg: Vec, // Out-degrees of nodes - path: LinkedList, // Stores the Eulerian path - graph: Vec>, // Adjacency list +/// Struct to represent the solver for finding an Eulerian path in a directed graph. +pub struct EulerianPathSolver { + node_count: usize, + edge_count: usize, + in_degrees: Vec, + out_degrees: Vec, + eulerian_path: LinkedList, + adjacency_list: Vec>, } -impl EulerianPath { - /// Creates a new instance of `EulerianPath` for the given graph. +impl EulerianPathSolver { + /// Creates a new instance of `EulerianPathSolver`. /// /// # Arguments /// - /// * `graph` - A directed graph represented as an adjacency list. + /// * `adjacency_list` - The graph represented as an adjacency list. /// /// # Returns /// - /// A new `EulerianPath` instance. - pub fn new(graph: Vec>) -> Self { + /// A new instance of `EulerianPathSolver`. + pub fn new(adjacency_list: Vec>) -> Self { Self { - nodes: graph.len(), - edges: 0, - in_deg: vec![0; graph.len()], - out_deg: vec![0; graph.len()], - path: LinkedList::new(), - graph, + node_count: adjacency_list.len(), + edge_count: 0, + in_degrees: vec![0; adjacency_list.len()], + out_degrees: vec![0; adjacency_list.len()], + eulerian_path: LinkedList::new(), + adjacency_list, } } - /// Finds an Eulerian path if it exists. + /// Computes the Eulerian path if it exists. /// /// # Returns /// - /// An `Option` containing the Eulerian path as a vector if it exists, or `None` otherwise. - fn find_eulerian_path(&mut self) -> Option> { - self.init_degrees(); + /// An `Option>` containing the Eulerian path if found; otherwise, `None`. + /// If multiple Eulerian paths exist, the one found will be returned, but it may not be unique. + fn find_path(&mut self) -> Option> { + self.initialize_degrees(); if !self.has_eulerian_path() { return None; } - let start = self.find_start(); - self.dfs(start); + let start_node = self.get_start_node(); + self.depth_first_search(start_node); - if self.path.len() != self.edges + 1 { + if self.eulerian_path.len() != self.edge_count + 1 { return None; } - let mut solution = Vec::with_capacity(self.edges + 1); - while let Some(node) = self.path.pop_front() { - solution.push(node); + let mut path = Vec::with_capacity(self.edge_count + 1); + while let Some(node) = self.eulerian_path.pop_front() { + path.push(node); } - Some(solution) + Some(path) } - /// Initializes in-degrees, out-degrees, and counts the total number of edges. - fn init_degrees(&mut self) { - for (u, neighbors) in self.graph.iter().enumerate() { - for &v in neighbors { - self.in_deg[v] += 1; - self.out_deg[u] += 1; - self.edges += 1; + /// Initializes in-degrees and out-degrees for each node and counts total edges. + fn initialize_degrees(&mut self) { + for (start_node, neighbors) in self.adjacency_list.iter().enumerate() { + for &end_node in neighbors { + self.in_degrees[end_node] += 1; + self.out_degrees[start_node] += 1; + self.edge_count += 1; } } } - /// Checks if the graph has an Eulerian path. + /// Checks if an Eulerian path is possible in the graph. /// /// # Returns /// - /// `true` if an Eulerian path exists, `false` otherwise. + /// `true` if an Eulerian path exists; otherwise, `false`. fn has_eulerian_path(&self) -> bool { - if self.edges == 0 { + if self.edge_count == 0 { return false; } - let (mut start, mut end) = (0, 0); - for i in 0..self.nodes { - let (in_deg, out_deg) = (self.in_deg[i] as isize, self.out_deg[i] as isize); - match out_deg - in_deg { - 1 => start += 1, - -1 => end += 1, - d if d.abs() > 1 => return false, + let (mut start_nodes, mut end_nodes) = (0, 0); + for i in 0..self.node_count { + let (in_degree, out_degree) = + (self.in_degrees[i] as isize, self.out_degrees[i] as isize); + match out_degree - in_degree { + 1 => start_nodes += 1, + -1 => end_nodes += 1, + degree_diff if degree_diff.abs() > 1 => return false, _ => (), } } - (start == 0 && end == 0) || (start == 1 && end == 1) + (start_nodes == 0 && end_nodes == 0) || (start_nodes == 1 && end_nodes == 1) } - /// Finds the start node for the Eulerian path. + /// Finds the starting node for the Eulerian path. /// /// # Returns /// - /// The index of the start node. - fn find_start(&self) -> usize { - for i in 0..self.nodes { - if self.out_deg[i] > self.in_deg[i] { + /// The index of the starting node. + fn get_start_node(&self) -> usize { + for i in 0..self.node_count { + if self.out_degrees[i] > self.in_degrees[i] { return i; } } - (0..self.nodes).find(|&i| self.out_deg[i] > 0).unwrap_or(0) + (0..self.node_count) + .find(|&i| self.out_degrees[i] > 0) + .unwrap_or(0) } - /// Depth-first search traversal to construct the Eulerian path. + /// Performs depth-first search to construct the Eulerian path. /// /// # Arguments /// - /// * `u` - The current node being traversed. - fn dfs(&mut self, u: usize) { - while self.out_deg[u] > 0 { - let v = self.graph[u][self.out_deg[u] - 1]; - self.out_deg[u] -= 1; - self.dfs(v); + /// * `curr_node` - The current node being visited in the DFS traversal. + fn depth_first_search(&mut self, curr_node: usize) { + while self.out_degrees[curr_node] > 0 { + let next_node = self.adjacency_list[curr_node][self.out_degrees[curr_node] - 1]; + self.out_degrees[curr_node] -= 1; + self.depth_first_search(next_node); } - self.path.push_front(u); + self.eulerian_path.push_front(curr_node); } } From 6ca394fa11886d692bff32cd785d035ac75ebf39 Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Fri, 11 Oct 2024 10:23:49 +0700 Subject: [PATCH 4/4] chore: update docstring --- src/graph/eulerian_path.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/graph/eulerian_path.rs b/src/graph/eulerian_path.rs index 6c3f400461b..d37ee053f43 100644 --- a/src/graph/eulerian_path.rs +++ b/src/graph/eulerian_path.rs @@ -55,11 +55,12 @@ impl EulerianPathSolver { } } - /// Computes the Eulerian path if it exists. + /// Find the Eulerian path if it exists. /// /// # Returns /// /// An `Option>` containing the Eulerian path if found; otherwise, `None`. + /// /// If multiple Eulerian paths exist, the one found will be returned, but it may not be unique. fn find_path(&mut self) -> Option> { self.initialize_degrees(); @@ -94,7 +95,7 @@ impl EulerianPathSolver { } } - /// Checks if an Eulerian path is possible in the graph. + /// Checks if an Eulerian path exists in the graph. /// /// # Returns ///