Skip to content

Commit 510bbb3

Browse files
sozelfistvil02
andauthored
Implement Graph Coloring using Backtracking (#737)
* feat: implement Graph Coloring * ref: refactor implementation - Use `Vec<Vec<bool>>` to represent the adjacency matrix to make the code more readable and slightly more efficient in terms of memory usage. - Use `std::mem::take` to avoid unnecessary cloning while maintaining the original behavior of the algorithm - Add some edge tests * ref: refactor implementation - Add custom error `GraphColoringError` to handle the exceptional cases where the graph is empty or not squared - Adjust the `is_valid_color` method to make the implementation handle the directed graphs - Add tests for directed graphs * chore: rename `adj_matrix` to `adjacency_matrix` * chore: rename custom error variants * chore: add some more tests * chore: use 0 as first color --------- Co-authored-by: Piotr Idzik <[email protected]>
1 parent f8096d2 commit 510bbb3

File tree

2 files changed

+375
-0
lines changed

2 files changed

+375
-0
lines changed

src/backtracking/graph_coloring.rs

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
//! This module provides functionality for generating all possible colorings of a undirected (or directed) graph
2+
//! given a certain number of colors. It includes the GraphColoring struct and methods
3+
//! for validating color assignments and finding all valid colorings.
4+
5+
/// Represents potential errors when coloring on an adjacency matrix.
6+
#[derive(Debug, PartialEq, Eq)]
7+
pub enum GraphColoringError {
8+
// Indicates that the adjacency matrix is empty
9+
EmptyAdjacencyMatrix,
10+
// Indicates that the adjacency matrix is not squared
11+
ImproperAdjacencyMatrix,
12+
}
13+
14+
/// Generates all possible valid colorings of a graph.
15+
///
16+
/// # Arguments
17+
///
18+
/// * `adjacency_matrix` - A 2D vector representing the adjacency matrix of the graph.
19+
/// * `num_colors` - The number of colors available for coloring the graph.
20+
///
21+
/// # Returns
22+
///
23+
/// * A `Result` containing an `Option` with a vector of solutions or a `GraphColoringError` if
24+
/// there is an issue with the matrix.
25+
pub fn generate_colorings(
26+
adjacency_matrix: Vec<Vec<bool>>,
27+
num_colors: usize,
28+
) -> Result<Option<Vec<Vec<usize>>>, GraphColoringError> {
29+
GraphColoring::new(adjacency_matrix)?.find_solutions(num_colors)
30+
}
31+
32+
/// A struct representing a graph coloring problem.
33+
struct GraphColoring {
34+
// The adjacency matrix of the graph
35+
adjacency_matrix: Vec<Vec<bool>>,
36+
// The current colors assigned to each vertex
37+
vertex_colors: Vec<usize>,
38+
// Vector of all valid color assignments for the vertices found during the search
39+
solutions: Vec<Vec<usize>>,
40+
}
41+
42+
impl GraphColoring {
43+
/// Creates a new GraphColoring instance.
44+
///
45+
/// # Arguments
46+
///
47+
/// * `adjacency_matrix` - A 2D vector representing the adjacency matrix of the graph.
48+
///
49+
/// # Returns
50+
///
51+
/// * A new instance of GraphColoring or a `GraphColoringError` if the matrix is empty or non-square.
52+
fn new(adjacency_matrix: Vec<Vec<bool>>) -> Result<Self, GraphColoringError> {
53+
let num_vertices = adjacency_matrix.len();
54+
55+
// Check if the adjacency matrix is empty
56+
if num_vertices == 0 {
57+
return Err(GraphColoringError::EmptyAdjacencyMatrix);
58+
}
59+
60+
// Check if the adjacency matrix is square
61+
if adjacency_matrix.iter().any(|row| row.len() != num_vertices) {
62+
return Err(GraphColoringError::ImproperAdjacencyMatrix);
63+
}
64+
65+
Ok(GraphColoring {
66+
adjacency_matrix,
67+
vertex_colors: vec![usize::MAX; num_vertices],
68+
solutions: Vec::new(),
69+
})
70+
}
71+
72+
/// Returns the number of vertices in the graph.
73+
fn num_vertices(&self) -> usize {
74+
self.adjacency_matrix.len()
75+
}
76+
77+
/// Checks if a given color can be assigned to a vertex without causing a conflict.
78+
///
79+
/// # Arguments
80+
///
81+
/// * `vertex` - The index of the vertex to be colored.
82+
/// * `color` - The color to be assigned to the vertex.
83+
///
84+
/// # Returns
85+
///
86+
/// * `true` if the color can be assigned to the vertex, `false` otherwise.
87+
fn is_color_valid(&self, vertex: usize, color: usize) -> bool {
88+
for neighbor in 0..self.num_vertices() {
89+
// Check outgoing edges from vertex and incoming edges to vertex
90+
if (self.adjacency_matrix[vertex][neighbor] || self.adjacency_matrix[neighbor][vertex])
91+
&& self.vertex_colors[neighbor] == color
92+
{
93+
return false;
94+
}
95+
}
96+
true
97+
}
98+
99+
/// Recursively finds all valid colorings for the graph.
100+
///
101+
/// # Arguments
102+
///
103+
/// * `vertex` - The current vertex to be colored.
104+
/// * `num_colors` - The number of colors available for coloring the graph.
105+
fn find_colorings(&mut self, vertex: usize, num_colors: usize) {
106+
if vertex == self.num_vertices() {
107+
self.solutions.push(self.vertex_colors.clone());
108+
return;
109+
}
110+
111+
for color in 0..num_colors {
112+
if self.is_color_valid(vertex, color) {
113+
self.vertex_colors[vertex] = color;
114+
self.find_colorings(vertex + 1, num_colors);
115+
self.vertex_colors[vertex] = usize::MAX;
116+
}
117+
}
118+
}
119+
120+
/// Finds all solutions for the graph coloring problem.
121+
///
122+
/// # Arguments
123+
///
124+
/// * `num_colors` - The number of colors available for coloring the graph.
125+
///
126+
/// # Returns
127+
///
128+
/// * A `Result` containing an `Option` with a vector of solutions or a `GraphColoringError`.
129+
fn find_solutions(
130+
&mut self,
131+
num_colors: usize,
132+
) -> Result<Option<Vec<Vec<usize>>>, GraphColoringError> {
133+
self.find_colorings(0, num_colors);
134+
if self.solutions.is_empty() {
135+
Ok(None)
136+
} else {
137+
Ok(Some(std::mem::take(&mut self.solutions)))
138+
}
139+
}
140+
}
141+
142+
#[cfg(test)]
143+
mod tests {
144+
use super::*;
145+
146+
macro_rules! test_graph_coloring {
147+
($($name:ident: $test_case:expr,)*) => {
148+
$(
149+
#[test]
150+
fn $name() {
151+
let (adjacency_matrix, num_colors, expected) = $test_case;
152+
let actual = generate_colorings(adjacency_matrix, num_colors);
153+
assert_eq!(actual, expected);
154+
}
155+
)*
156+
};
157+
}
158+
159+
test_graph_coloring! {
160+
test_complete_graph_with_3_colors: (
161+
vec![
162+
vec![false, true, true, true],
163+
vec![true, false, true, false],
164+
vec![true, true, false, true],
165+
vec![true, false, true, false],
166+
],
167+
3,
168+
Ok(Some(vec![
169+
vec![0, 1, 2, 1],
170+
vec![0, 2, 1, 2],
171+
vec![1, 0, 2, 0],
172+
vec![1, 2, 0, 2],
173+
vec![2, 0, 1, 0],
174+
vec![2, 1, 0, 1],
175+
]))
176+
),
177+
test_linear_graph_with_2_colors: (
178+
vec![
179+
vec![false, true, false, false],
180+
vec![true, false, true, false],
181+
vec![false, true, false, true],
182+
vec![false, false, true, false],
183+
],
184+
2,
185+
Ok(Some(vec![
186+
vec![0, 1, 0, 1],
187+
vec![1, 0, 1, 0],
188+
]))
189+
),
190+
test_incomplete_graph_with_insufficient_colors: (
191+
vec![
192+
vec![false, true, true],
193+
vec![true, false, true],
194+
vec![true, true, false],
195+
],
196+
1,
197+
Ok(None::<Vec<Vec<usize>>>)
198+
),
199+
test_empty_graph: (
200+
vec![],
201+
1,
202+
Err(GraphColoringError::EmptyAdjacencyMatrix)
203+
),
204+
test_non_square_matrix: (
205+
vec![
206+
vec![false, true, true],
207+
vec![true, false, true],
208+
],
209+
3,
210+
Err(GraphColoringError::ImproperAdjacencyMatrix)
211+
),
212+
test_single_vertex_graph: (
213+
vec![
214+
vec![false],
215+
],
216+
1,
217+
Ok(Some(vec![
218+
vec![0],
219+
]))
220+
),
221+
test_bipartite_graph_with_2_colors: (
222+
vec![
223+
vec![false, true, false, true],
224+
vec![true, false, true, false],
225+
vec![false, true, false, true],
226+
vec![true, false, true, false],
227+
],
228+
2,
229+
Ok(Some(vec![
230+
vec![0, 1, 0, 1],
231+
vec![1, 0, 1, 0],
232+
]))
233+
),
234+
test_large_graph_with_3_colors: (
235+
vec![
236+
vec![false, true, true, false, true, true, false, true, true, false],
237+
vec![true, false, true, true, false, true, true, false, true, true],
238+
vec![true, true, false, true, true, false, true, true, false, true],
239+
vec![false, true, true, false, true, true, false, true, true, false],
240+
vec![true, false, true, true, false, true, true, false, true, true],
241+
vec![true, true, false, true, true, false, true, true, false, true],
242+
vec![false, true, true, false, true, true, false, true, true, false],
243+
vec![true, false, true, true, false, true, true, false, true, true],
244+
vec![true, true, false, true, true, false, true, true, false, true],
245+
vec![false, true, true, false, true, true, false, true, true, false],
246+
],
247+
3,
248+
Ok(Some(vec![
249+
vec![0, 1, 2, 0, 1, 2, 0, 1, 2, 0],
250+
vec![0, 2, 1, 0, 2, 1, 0, 2, 1, 0],
251+
vec![1, 0, 2, 1, 0, 2, 1, 0, 2, 1],
252+
vec![1, 2, 0, 1, 2, 0, 1, 2, 0, 1],
253+
vec![2, 0, 1, 2, 0, 1, 2, 0, 1, 2],
254+
vec![2, 1, 0, 2, 1, 0, 2, 1, 0, 2],
255+
]))
256+
),
257+
test_disconnected_graph: (
258+
vec![
259+
vec![false, false, false],
260+
vec![false, false, false],
261+
vec![false, false, false],
262+
],
263+
2,
264+
Ok(Some(vec![
265+
vec![0, 0, 0],
266+
vec![0, 0, 1],
267+
vec![0, 1, 0],
268+
vec![0, 1, 1],
269+
vec![1, 0, 0],
270+
vec![1, 0, 1],
271+
vec![1, 1, 0],
272+
vec![1, 1, 1],
273+
]))
274+
),
275+
test_no_valid_coloring: (
276+
vec![
277+
vec![false, true, true],
278+
vec![true, false, true],
279+
vec![true, true, false],
280+
],
281+
2,
282+
Ok(None::<Vec<Vec<usize>>>)
283+
),
284+
test_more_colors_than_nodes: (
285+
vec![
286+
vec![true, true],
287+
vec![true, true],
288+
],
289+
3,
290+
Ok(Some(vec![
291+
vec![0, 1],
292+
vec![0, 2],
293+
vec![1, 0],
294+
vec![1, 2],
295+
vec![2, 0],
296+
vec![2, 1],
297+
]))
298+
),
299+
test_no_coloring_with_zero_colors: (
300+
vec![
301+
vec![true],
302+
],
303+
0,
304+
Ok(None::<Vec<Vec<usize>>>)
305+
),
306+
test_complete_graph_with_3_vertices_and_3_colors: (
307+
vec![
308+
vec![false, true, true],
309+
vec![true, false, true],
310+
vec![true, true, false],
311+
],
312+
3,
313+
Ok(Some(vec![
314+
vec![0, 1, 2],
315+
vec![0, 2, 1],
316+
vec![1, 0, 2],
317+
vec![1, 2, 0],
318+
vec![2, 0, 1],
319+
vec![2, 1, 0],
320+
]))
321+
),
322+
test_directed_graph_with_3_colors: (
323+
vec![
324+
vec![false, true, false, true],
325+
vec![false, false, true, false],
326+
vec![true, false, false, true],
327+
vec![true, false, false, false],
328+
],
329+
3,
330+
Ok(Some(vec![
331+
vec![0, 1, 2, 1],
332+
vec![0, 2, 1, 2],
333+
vec![1, 0, 2, 0],
334+
vec![1, 2, 0, 2],
335+
vec![2, 0, 1, 0],
336+
vec![2, 1, 0, 1],
337+
]))
338+
),
339+
test_directed_graph_no_valid_coloring: (
340+
vec![
341+
vec![false, true, false, true],
342+
vec![false, false, true, true],
343+
vec![true, false, false, true],
344+
vec![true, false, false, false],
345+
],
346+
3,
347+
Ok(None::<Vec<Vec<usize>>>)
348+
),
349+
test_large_directed_graph_with_3_colors: (
350+
vec![
351+
vec![false, true, false, false, true, false, false, true, false, false],
352+
vec![false, false, true, false, false, true, false, false, true, false],
353+
vec![false, false, false, true, false, false, true, false, false, true],
354+
vec![true, false, false, false, true, false, false, true, false, false],
355+
vec![false, true, false, false, false, true, false, false, true, false],
356+
vec![false, false, true, false, false, false, true, false, false, true],
357+
vec![true, false, false, false, true, false, false, true, false, false],
358+
vec![false, true, false, false, false, true, false, false, true, false],
359+
vec![false, false, true, false, false, false, true, false, false, true],
360+
vec![true, false, false, false, true, false, false, true, false, false],
361+
],
362+
3,
363+
Ok(Some(vec![
364+
vec![0, 1, 2, 1, 2, 0, 1, 2, 0, 1],
365+
vec![0, 2, 1, 2, 1, 0, 2, 1, 0, 2],
366+
vec![1, 0, 2, 0, 2, 1, 0, 2, 1, 0],
367+
vec![1, 2, 0, 2, 0, 1, 2, 0, 1, 2],
368+
vec![2, 0, 1, 0, 1, 2, 0, 1, 2, 0],
369+
vec![2, 1, 0, 1, 0, 2, 1, 0, 2, 1]
370+
]))
371+
),
372+
}
373+
}

src/backtracking/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod all_combination_of_size_k;
2+
mod graph_coloring;
23
mod hamiltonian_cycle;
34
mod knight_tour;
45
mod n_queens;
@@ -8,6 +9,7 @@ mod rat_in_maze;
89
mod sudoku;
910

1011
pub use all_combination_of_size_k::generate_all_combinations;
12+
pub use graph_coloring::generate_colorings;
1113
pub use hamiltonian_cycle::find_hamiltonian_cycle;
1214
pub use knight_tour::find_knight_tour;
1315
pub use n_queens::n_queens_solver;

0 commit comments

Comments
 (0)