From 4de67b014c626e8a5f22dde8ffa9f8989b6f79f1 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Mon, 30 Sep 2024 20:35:42 +0530 Subject: [PATCH] Improve comments, add doctests to coloring.py --- backtracking/coloring.py | 103 ++++++++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/backtracking/coloring.py b/backtracking/coloring.py index f10cdbcf9..e0d3c9e52 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -1,7 +1,7 @@ """ -Graph Coloring also called "m coloring problem" -consists of coloring a given graph with at most m colors -such that no adjacent vertices are assigned the same color +Graph Coloring (also called the "m coloring problem") is the problem of +assigning at most 'm' colors to the vertices of a graph such that +no two adjacent vertices share the same color. Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring """ @@ -11,13 +11,27 @@ def valid_coloring( neighbours: list[int], colored_vertices: list[int], color: int ) -> bool: """ + This function checks if a given vertex can be assigned the specified color + without violating the graph coloring constraints (i.e., no two adjacent vertices + have the same color). + + Procedure: For each neighbour check if the coloring constraint is satisfied If any of the neighbours fail the constraint return False If all neighbours validate the constraint return True - >>> neighbours = [0,1,0,1,0] - >>> colored_vertices = [0, 2, 1, 2, 0] + Parameters: + neighbours (list[int]): The list representing which vertices are adjacent to the current vertex. + 1 indicates an edge between the current vertex and the neighbour. + colored_vertices (list[int]): List of current color assignments for all vertices (-1 means uncolored). + color (int): The color we are trying to assign to the current vertex. + Returns: + bool: True if the vertex can be safely colored with the given color, otherwise False. + + Examples: + >>> neighbours = [0, 1, 0, 1, 0] + >>> colored_vertices = [0, 2, 1, 2, 0] >>> color = 1 >>> valid_coloring(neighbours, colored_vertices, color) True @@ -25,8 +39,14 @@ def valid_coloring( >>> color = 2 >>> valid_coloring(neighbours, colored_vertices, color) False + + >>> neighbors = [1, 0, 1, 0] + >>> colored_vertices = [-1, -1, -1, -1] + >>> color = 0 + >>> valid_coloring(neighbors, colored_vertices, color) + True """ - # Does any neighbour not satisfy the constraints + # Check if any adjacent vertex has already been colored with the same color return not any( neighbour == 1 and colored_vertices[i] == color for i, neighbour in enumerate(neighbours) @@ -37,7 +57,7 @@ def util_color( graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int ) -> bool: """ - Pseudo-Code + Recursive function to try and color the graph using backtracking. Base Case: 1. Check if coloring is complete @@ -51,6 +71,18 @@ def util_color( 2.4. if current coloring leads to a solution return 2.5. Uncolor given vertex + Parameters: + graph (list of list of int): Adjacency matrix representing the graph. + graph[i][j] is 1 if there is an edge between vertex i and j. + max_colors (int): Maximum number of colors allowed (m in the m-coloring problem). + colored_vertices (list of int): Current color assignments for each vertex. + -1 indicates that the vertex has not been colored yet. + index (int): The current vertex index being processed. + + Returns: + bool: True if the graph can be colored using at most max_colors, otherwise False. + + Examples: >>> graph = [[0, 1, 0, 0, 0], ... [1, 0, 1, 0, 1], ... [0, 1, 0, 1, 0], @@ -67,36 +99,45 @@ def util_color( >>> util_color(graph, max_colors, colored_vertices, index) False """ - - # Base Case + # Base Case: If all vertices have been assigned a color, we have a valid solution if index == len(graph): return True - # Recursive Step - for i in range(max_colors): - if valid_coloring(graph[index], colored_vertices, i): - # Color current vertex - colored_vertices[index] = i - # Validate coloring + # Try each color for the current vertex + for color in range(max_colors): + # Check if it's valid to color the current vertex with 'color' + if valid_coloring(graph[index], colored_vertices, color): + colored_vertices[index] = color # Assign color + # Recur to color the rest of the vertices if util_color(graph, max_colors, colored_vertices, index + 1): return True - # Backtrack + # Backtrack if no solution found with the current assignment colored_vertices[index] = -1 - return False + + return False # Return False if no valid coloring is possible def color(graph: list[list[int]], max_colors: int) -> list[int]: """ - Wrapper function to call subroutine called util_color - which will either return True or False. - If True is returned colored_vertices list is filled with correct colorings + Attempts to color the graph with at most max_colors colors such that no two adjacent + vertices have the same color. If it is possible, returns the list of color assignments; + otherwise, returns an empty list. + Parameters: + graph (list of list of int): Adjacency matrix representing the graph. + max_colors (int): Maximum number of colors allowed. + + Returns: + list of int: List of color assignments if the graph can be colored using max_colors. + Each index in the list represents the color assigned to the corresponding vertex. + If coloring is not possible, returns an empty list. + + Examples: >>> graph = [[0, 1, 0, 0, 0], ... [1, 0, 1, 0, 1], ... [0, 1, 0, 1, 0], ... [0, 1, 1, 0, 0], ... [0, 1, 0, 0, 0]] - >>> max_colors = 3 >>> color(graph, max_colors) [0, 1, 0, 2, 0] @@ -104,10 +145,26 @@ def color(graph: list[list[int]], max_colors: int) -> list[int]: >>> max_colors = 2 >>> color(graph, max_colors) [] + + >>> graph = [[0, 1], [1, 0]] # Simple 2-node graph + >>> max_colors = 2 + >>> color(graph, max_colors) + [0, 1] + + >>> graph = [[0, 1, 1], [1, 0, 1], [1, 1, 0]] # Complete graph of 3 vertices + >>> max_colors = 2 + >>> color(graph, max_colors) + [] + + >>> max_colors = 3 + >>> color(graph, max_colors) + [0, 1, 2] """ + # Initialize all vertices as uncolored (-1) colored_vertices = [-1] * len(graph) + # Use the utility function to try and color the graph starting from vertex 0 if util_color(graph, max_colors, colored_vertices, 0): - return colored_vertices + return colored_vertices # Return the successful color assignment - return [] + return [] # Return an empty list if no valid coloring is possible