diff --git a/backtracking/coloring.py b/backtracking/coloring.py index f10cdbcf9..79fafed19 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,31 @@ def valid_coloring( neighbours: list[int], colored_vertices: list[int], color: int ) -> bool: """ + Check 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: 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 of current color assignments for all vertices + (-1 means uncolored). + color: The color we are trying to assign to the current vertex. + Returns: + 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 +43,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 +61,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 +75,20 @@ def util_color( 2.4. if current coloring leads to a solution return 2.5. Uncolor given vertex + Parameters: + graph: Adjacency matrix representing the graph. + graph[i][j] is 1 if there is an edge + between vertex i and j. + max_colors: Maximum number of colors allowed (m in the m-coloring problem). + colored_vertices: Current color assignments for each vertex. + -1 indicates that the vertex has not been colored + yet. + index: The current vertex index being processed. + + Returns: + 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 +105,47 @@ 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 + Attempt 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: Adjacency matrix representing the graph. + max_colors: Maximum number of colors allowed. + + Returns: + 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 +153,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 # The successful color assignment - return [] + return [] # No valid coloring is possible