From 7e685935db9add12f39eac06b8c589fc66d7e794 Mon Sep 17 00:00:00 2001 From: ethanhaines <122860615+ethanhaines@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:02:06 -0400 Subject: [PATCH 1/2] Add Graph Coloring implementation alongside tests --- .../GraphColoring/GraphColoringSolverTests.cs | 423 ++++++++++++++++++ .../GraphColoring/GraphColoringSolver.cs | 184 ++++++++ README.md | 2 + 3 files changed, 609 insertions(+) create mode 100644 Algorithms.Tests/Problems/GraphColoring/GraphColoringSolverTests.cs create mode 100644 Algorithms/Problems/GraphColoring/GraphColoringSolver.cs diff --git a/Algorithms.Tests/Problems/GraphColoring/GraphColoringSolverTests.cs b/Algorithms.Tests/Problems/GraphColoring/GraphColoringSolverTests.cs new file mode 100644 index 00000000..9bb6feef --- /dev/null +++ b/Algorithms.Tests/Problems/GraphColoring/GraphColoringSolverTests.cs @@ -0,0 +1,423 @@ +using Algorithms.Problems.GraphColoring; + +namespace Algorithms.Tests.Problems.GraphColoring; + +[TestFixture] +public sealed class GraphColoringSolverTests +{ + /// + /// Helper method to validate that a coloring is valid for a given graph. + /// + /// The graph adjacency matrix. + /// The color assignment to validate. + /// + /// A valid coloring must satisfy: + /// 1. All vertices are colored (no -1 values). + /// 2. No two adjacent vertices have the same color. + /// + private static void AssertValidColoring(bool[,] adjacencyMatrix, int[] colors) + { + var numVertices = adjacencyMatrix.GetLength(0); + + Assert.That(colors, Has.Length.EqualTo(numVertices), + "Color array length must match number of vertices."); + + // Check all vertices are colored + for (var i = 0; i < numVertices; i++) + { + Assert.That(colors[i], Is.GreaterThanOrEqualTo(0), + $"Vertex {i} is not colored (has value -1)."); + } + + // Check no adjacent vertices have the same color + for (var i = 0; i < numVertices; i++) + { + for (var j = i + 1; j < numVertices; j++) + { + if (adjacencyMatrix[i, j]) + { + Assert.That(colors[i], Is.Not.EqualTo(colors[j]), + $"Adjacent vertices {i} and {j} have the same color {colors[i]}."); + } + } + } + } + + /// + /// Helper method to create an empty graph (no edges). + /// + private static bool[,] CreateEmptyGraph(int vertices) + { + return new bool[vertices, vertices]; + } + + /// + /// Helper method to create a complete graph where all vertices are connected. + /// + private static bool[,] CreateCompleteGraph(int vertices) + { + var graph = new bool[vertices, vertices]; + for (var i = 0; i < vertices; i++) + { + for (var j = 0; j < vertices; j++) + { + if (i != j) + { + graph[i, j] = true; + } + } + } + return graph; + } + + /// + /// Helper method to create a bipartite graph (two sets with edges only between sets). + /// + private static bool[,] CreateBipartiteGraph(int setASize, int setBSize) + { + var total = setASize + setBSize; + var graph = new bool[total, total]; + + // Connect all vertices in set A to all vertices in set B + for (var i = 0; i < setASize; i++) + { + for (var j = setASize; j < total; j++) + { + graph[i, j] = true; + graph[j, i] = true; + } + } + return graph; + } + + /// + /// Helper method to create a cycle graph. + /// + private static bool[,] CreateCycleGraph(int vertices) + { + var graph = new bool[vertices, vertices]; + for (var i = 0; i < vertices; i++) + { + var next = (i + 1) % vertices; + graph[i, next] = true; + graph[next, i] = true; + } + return graph; + } + + /// + /// Helper method to create a path graph (linear chain). + /// + private static bool[,] CreatePathGraph(int vertices) + { + var graph = new bool[vertices, vertices]; + for (var i = 0; i < vertices - 1; i++) + { + graph[i, i + 1] = true; + graph[i + 1, i] = true; + } + return graph; + } + + [Test] + public void ColorGraph_ThrowsArgumentNullException_WhenAdjacencyMatrixIsNull() + { + var solver = new GraphColoringSolver(); + + Assert.Throws(() => solver.ColorGraph(null!, 3)); + } + + [Test] + public void ColorGraph_ThrowsArgumentException_WhenAdjacencyMatrixIsNotSquare() + { + var solver = new GraphColoringSolver(); + var nonSquareMatrix = new bool[3, 4]; + + Assert.Throws(() => solver.ColorGraph(nonSquareMatrix, 3)); + } + + [TestCase(0)] + [TestCase(-1)] + [TestCase(-5)] + public void ColorGraph_ThrowsArgumentException_WhenNumColorsIsNonPositive(int numColors) + { + var solver = new GraphColoringSolver(); + var graph = CreateEmptyGraph(3); + + Assert.Throws(() => solver.ColorGraph(graph, numColors)); + } + + [Test] + public void ColorGraph_ReturnsEmptyArray_ForEmptyGraph() + { + var solver = new GraphColoringSolver(); + var graph = new bool[0, 0]; + + var result = solver.ColorGraph(graph, 1); + + Assert.That(result, Is.Empty); + } + + [Test] + public void ColorGraph_ColorsSingleVertex_WithOneColor() + { + var solver = new GraphColoringSolver(); + var graph = CreateEmptyGraph(1); + + var result = solver.ColorGraph(graph, 1); + + Assert.That(result, Has.Length.EqualTo(1)); + Assert.That(result[0], Is.EqualTo(0)); + AssertValidColoring(graph, result); + } + + [Test] + public void ColorGraph_ColorsDisconnectedVertices_WithOneColor() + { + var solver = new GraphColoringSolver(); + var graph = CreateEmptyGraph(5); // No edges + + var result = solver.ColorGraph(graph, 1); + + Assert.That(result, Has.Length.EqualTo(5)); + AssertValidColoring(graph, result); + + // All vertices should have the same color since there are no edges + Assert.That(result.Distinct().Count(), Is.EqualTo(1)); + } + + [Test] + public void ColorGraph_ColorsBipartiteGraph_WithTwoColors() + { + var solver = new GraphColoringSolver(); + var graph = CreateBipartiteGraph(3, 3); + + var result = solver.ColorGraph(graph, 2); + + Assert.That(result, Has.Length.EqualTo(6)); + AssertValidColoring(graph, result); + + // Bipartite graph should use exactly 2 colors + Assert.That(result.Distinct().Count(), Is.LessThanOrEqualTo(2)); + } + + [Test] + public void ColorGraph_ThrowsArgumentException_ForBipartiteGraphWithOneColor() + { + var solver = new GraphColoringSolver(); + var graph = CreateBipartiteGraph(2, 2); + + // Bipartite graph with edges requires at least 2 colors + Assert.Throws(() => solver.ColorGraph(graph, 1)); + } + + [Test] + public void ColorGraph_ColorsPathGraph_WithTwoColors() + { + var solver = new GraphColoringSolver(); + var graph = CreatePathGraph(5); + + var result = solver.ColorGraph(graph, 2); + + Assert.That(result, Has.Length.EqualTo(5)); + AssertValidColoring(graph, result); + + // Path graph can be colored with 2 colors + Assert.That(result.Distinct().Count(), Is.LessThanOrEqualTo(2)); + } + + [Test] + public void ColorGraph_ColorsEvenCycle_WithTwoColors() + { + var solver = new GraphColoringSolver(); + var graph = CreateCycleGraph(6); // Even cycle + + var result = solver.ColorGraph(graph, 2); + + Assert.That(result, Has.Length.EqualTo(6)); + AssertValidColoring(graph, result); + + // Even cycle can be colored with 2 colors + Assert.That(result.Distinct().Count(), Is.LessThanOrEqualTo(2)); + } + + [Test] + public void ColorGraph_ThrowsArgumentException_ForOddCycleWithTwoColors() + { + var solver = new GraphColoringSolver(); + var graph = CreateCycleGraph(5); // Odd cycle + + // Odd cycle requires at least 3 colors + Assert.Throws(() => solver.ColorGraph(graph, 2)); + } + + [Test] + public void ColorGraph_ColorsOddCycle_WithThreeColors() + { + var solver = new GraphColoringSolver(); + var graph = CreateCycleGraph(5); // Odd cycle + + var result = solver.ColorGraph(graph, 3); + + Assert.That(result, Has.Length.EqualTo(5)); + AssertValidColoring(graph, result); + } + + [Test] + public void ColorGraph_ColorsTriangle_WithThreeColors() + { + var solver = new GraphColoringSolver(); + var graph = CreateCompleteGraph(3); // Triangle (K3) + + var result = solver.ColorGraph(graph, 3); + + Assert.That(result, Has.Length.EqualTo(3)); + AssertValidColoring(graph, result); + + // Complete graph K3 requires exactly 3 colors + Assert.That(result.Distinct().Count(), Is.EqualTo(3)); + } + + [Test] + public void ColorGraph_ThrowsArgumentException_ForTriangleWithTwoColors() + { + var solver = new GraphColoringSolver(); + var graph = CreateCompleteGraph(3); // Triangle (K3) + + // Triangle requires 3 colors + Assert.Throws(() => solver.ColorGraph(graph, 2)); + } + + [Test] + public void ColorGraph_ColorsCompleteGraphK4_WithFourColors() + { + var solver = new GraphColoringSolver(); + var graph = CreateCompleteGraph(4); // K4 + + var result = solver.ColorGraph(graph, 4); + + Assert.That(result, Has.Length.EqualTo(4)); + AssertValidColoring(graph, result); + + // Complete graph K4 requires exactly 4 colors + Assert.That(result.Distinct().Count(), Is.EqualTo(4)); + } + + [Test] + public void ColorGraph_ThrowsArgumentException_ForCompleteGraphK4WithThreeColors() + { + var solver = new GraphColoringSolver(); + var graph = CreateCompleteGraph(4); // K4 + + // K4 requires 4 colors + Assert.Throws(() => solver.ColorGraph(graph, 3)); + } + + [Test] + public void ColorGraph_ColorsStarGraph_WithTwoColors() + { + var solver = new GraphColoringSolver(); + + // Star graph: one central vertex connected to all others + var graph = new bool[5, 5]; + for (var i = 1; i < 5; i++) + { + graph[0, i] = true; + graph[i, 0] = true; + } + + var result = solver.ColorGraph(graph, 2); + + Assert.That(result, Has.Length.EqualTo(5)); + AssertValidColoring(graph, result); + + // Star graph requires only 2 colors + Assert.That(result.Distinct().Count(), Is.LessThanOrEqualTo(2)); + } + + [Test] + public void ColorGraph_HandlesPetersenGraph_WithThreeColors() + { + var solver = new GraphColoringSolver(); + + // Simplified version: a graph that requires 3 colors + // Creating a graph with a triangle and additional connections + var graph = new bool[5, 5]; + + // Triangle: vertices 0, 1, 2 + graph[0, 1] = graph[1, 0] = true; + graph[1, 2] = graph[2, 1] = true; + graph[2, 0] = graph[0, 2] = true; + + // Additional edges to vertex 3 + graph[0, 3] = graph[3, 0] = true; + graph[1, 3] = graph[3, 1] = true; + + // Additional edges to vertex 4 + graph[2, 4] = graph[4, 2] = true; + graph[3, 4] = graph[4, 3] = true; + + var result = solver.ColorGraph(graph, 3); + + Assert.That(result, Has.Length.EqualTo(5)); + AssertValidColoring(graph, result); + } + + [Test] + public void ColorGraph_AllColorsWithinRange() + { + var solver = new GraphColoringSolver(); + var graph = CreateCompleteGraph(4); + var numColors = 4; + + var result = solver.ColorGraph(graph, numColors); + + // Verify all colors are in range [0, numColors) + foreach (var color in result) + { + Assert.That(color, Is.InRange(0, numColors - 1)); + } + } + + [Test] + public void ColorGraph_SymmetricMatrix_ProducesSameResult() + { + var solver = new GraphColoringSolver(); + + // Create a symmetric graph + var graph = new bool[4, 4]; + graph[0, 1] = graph[1, 0] = true; + graph[1, 2] = graph[2, 1] = true; + graph[2, 3] = graph[3, 2] = true; + + var result = solver.ColorGraph(graph, 3); + + Assert.That(result, Has.Length.EqualTo(4)); + AssertValidColoring(graph, result); + } + + [Test] + public void ColorGraph_LargerGraph_ProducesValidColoring() + { + var solver = new GraphColoringSolver(); + + // Create a larger graph (10 vertices, random edges) + var graph = new bool[10, 10]; + + // Create edges forming a more complex structure + for (var i = 0; i < 9; i++) + { + graph[i, i + 1] = graph[i + 1, i] = true; + } + + // Add some cross edges + graph[0, 5] = graph[5, 0] = true; + graph[2, 7] = graph[7, 2] = true; + graph[3, 8] = graph[8, 3] = true; + + var result = solver.ColorGraph(graph, 3); + + Assert.That(result, Has.Length.EqualTo(10)); + AssertValidColoring(graph, result); + } +} diff --git a/Algorithms/Problems/GraphColoring/GraphColoringSolver.cs b/Algorithms/Problems/GraphColoring/GraphColoringSolver.cs new file mode 100644 index 00000000..5bcf14b1 --- /dev/null +++ b/Algorithms/Problems/GraphColoring/GraphColoringSolver.cs @@ -0,0 +1,184 @@ +namespace Algorithms.Problems.GraphColoring; + +/// +/// Solves the Graph Coloring Problem using backtracking to assign colors to graph vertices +/// such that no two adjacent vertices share the same color. +/// +/// +/// +/// The Graph Coloring Problem is an NP-complete problem that aims to color the vertices +/// of a graph with the minimum number of colors such that no two adjacent vertices have +/// the same color. This implementation uses a backtracking algorithm to find a valid coloring +/// given a specific number of colors. +/// +/// +/// The algorithm attempts to assign colors to vertices one by one. If a color assignment +/// leads to a conflict, it backtracks and tries a different color. If no valid coloring +/// exists with the given number of colors, an exception is thrown. +/// +/// +/// Complexity: The worst-case time complexity is O(k^n) where n is the number of +/// vertices and k is the number of colors. This is an exponential algorithm suitable for +/// small to medium-sized graphs or graphs with special structures. +/// +/// +/// Applications: Graph coloring has numerous practical applications including: +/// register allocation in compilers, scheduling problems, frequency assignment in mobile networks, +/// and solving Sudoku puzzles. +/// +/// +/// For more information, see: +/// Graph Coloring on Wikipedia. +/// +/// +public sealed class GraphColoringSolver +{ + /// + /// Attempts to color a graph with the specified number of colors. + /// + /// + /// A square boolean matrix representing the graph where adjacencyMatrix[i, j] + /// is true if there is an edge between vertex i and vertex j. + /// The matrix must be symmetric for undirected graphs. + /// + /// The number of colors to use for coloring. Must be positive. + /// + /// An array where each element represents the color assigned to the corresponding vertex + /// (0-indexed colors from 0 to - 1). + /// + /// + /// Thrown when is null. + /// + /// + /// Thrown when the adjacency matrix is not square, when is non-positive, + /// or when no valid coloring exists with the given number of colors. + /// + /// + /// + /// This method finds the first valid coloring it encounters. Multiple valid colorings + /// may exist for a given graph, but only one is returned. + /// + /// + /// Example: For a triangle graph (3 vertices, all connected), at least 3 colors + /// are required. Calling this method with numColors = 2 will throw an exception, + /// while numColors = 3 will return a valid coloring such as [0, 1, 2]. + /// + /// + public int[] ColorGraph(bool[,] adjacencyMatrix, int numColors) + { + if (adjacencyMatrix is null) + { + throw new ArgumentNullException(nameof(adjacencyMatrix)); + } + + var numVertices = adjacencyMatrix.GetLength(0); + + if (numVertices != adjacencyMatrix.GetLength(1)) + { + throw new ArgumentException("Adjacency matrix must be square.", nameof(adjacencyMatrix)); + } + + if (numColors <= 0) + { + throw new ArgumentException("Number of colors must be positive.", nameof(numColors)); + } + + // Handle empty graph + if (numVertices == 0) + { + return Array.Empty(); + } + + var colors = new int[numVertices]; + + // Initialize all vertices as uncolored (-1) + for (var i = 0; i < numVertices; i++) + { + colors[i] = -1; + } + + if (!ColorVertex(adjacencyMatrix, colors, 0, numColors)) + { + throw new ArgumentException( + $"Graph cannot be colored with {numColors} color(s). " + + $"A larger number of colors may be required."); + } + + return colors; + } + + /// + /// Recursively attempts to color vertices using backtracking. + /// + /// The graph adjacency matrix. + /// Current color assignment for each vertex. + /// The current vertex to color. + /// The number of available colors. + /// true if a valid coloring is found; otherwise, false. + /// + /// This method tries each available color for the current vertex. If a color is valid + /// (doesn't conflict with adjacent vertices), it proceeds to color the next vertex. + /// If no valid color is found, it backtracks. + /// + private bool ColorVertex(bool[,] adjacencyMatrix, int[] colors, int vertex, int numColors) + { + var numVertices = adjacencyMatrix.GetLength(0); + + // Base case: all vertices are colored + if (vertex == numVertices) + { + return true; + } + + // Try each color for the current vertex + for (var color = 0; color < numColors; color++) + { + if (IsSafeToColor(adjacencyMatrix, colors, vertex, color)) + { + colors[vertex] = color; + + // Recursively color the next vertex + if (ColorVertex(adjacencyMatrix, colors, vertex + 1, numColors)) + { + return true; + } + + // Backtrack if the current color assignment doesn't lead to a solution + colors[vertex] = -1; + } + } + + return false; + } + + /// + /// Checks whether it is safe to assign a color to a vertex. + /// + /// The graph adjacency matrix. + /// Current color assignment for each vertex. + /// The vertex to check. + /// The color to assign. + /// + /// true if the color can be safely assigned (no adjacent vertex has this color); + /// otherwise, false. + /// + /// + /// A color is safe to assign if none of the already-colored adjacent vertices + /// have the same color. + /// + private bool IsSafeToColor(bool[,] adjacencyMatrix, int[] colors, int vertex, int color) + { + var numVertices = adjacencyMatrix.GetLength(0); + + for (var i = 0; i < numVertices; i++) + { + // Check if vertex i is adjacent to the current vertex and has the same color + if (adjacencyMatrix[vertex, i] && colors[i] == color) + { + return false; + } + } + + return true; + } +} diff --git a/README.md b/README.md index 1b5de66b..dffb50a8 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,8 @@ find more than one implementation for the same objective but using different alg * [Backtracking](./Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs) * [Knight Tour](./Algorithms/Problems/KnightTour/) * [Open Knight Tour](./Algorithms/Problems/KnightTour/OpenKnightTour.cs) + * [Graph Coloring](./Algorithms/Problems/GraphColoring) + * [Backtracking Graph Coloring Solver](./Algorithms/Problems/GraphColoring/GraphColoringSolver.cs) * [Dynamic Programming](./Algorithms/Problems/DynamicProgramming) * [Coin Change](./Algorithms/Problems/DynamicProgramming/CoinChange/DynamicCoinChangeSolver.cs) * [Levenshtein Distance](./Algorithms/Problems/DynamicProgramming/LevenshteinDistance/LevenshteinDistance.cs) From eebe7c62195f9fa5ab1267d635c4c4489ed407d5 Mon Sep 17 00:00:00 2001 From: Andrii Siriak Date: Fri, 31 Oct 2025 10:02:57 +0200 Subject: [PATCH 2/2] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Algorithms/Problems/GraphColoring/GraphColoringSolver.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Algorithms/Problems/GraphColoring/GraphColoringSolver.cs b/Algorithms/Problems/GraphColoring/GraphColoringSolver.cs index 5bcf14b1..26034fe7 100644 --- a/Algorithms/Problems/GraphColoring/GraphColoringSolver.cs +++ b/Algorithms/Problems/GraphColoring/GraphColoringSolver.cs @@ -92,10 +92,7 @@ public int[] ColorGraph(bool[,] adjacencyMatrix, int numColors) var colors = new int[numVertices]; // Initialize all vertices as uncolored (-1) - for (var i = 0; i < numVertices; i++) - { - colors[i] = -1; - } + Array.Fill(colors, -1); if (!ColorVertex(adjacencyMatrix, colors, 0, numColors)) {