Python/backtracking/coloring.py
2024-10-01 15:26:34 +05:30

179 lines
5.8 KiB
Python

"""
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
"""
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
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
>>> 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
"""
# 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)
)
def util_color(
graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int
) -> bool:
"""
Recursive function to try and color the graph using backtracking.
Base Case:
1. Check if coloring is complete
1.1 If complete return True (meaning that we successfully colored the graph)
Recursive Step:
2. Iterates over each color:
Check if the current coloring is valid:
2.1. Color given vertex
2.2. Do recursive call, check if this coloring leads to a solution
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],
... [0, 1, 1, 0, 0],
... [0, 1, 0, 0, 0]]
>>> max_colors = 3
>>> colored_vertices = [0, 1, 0, 0, 0]
>>> index = 3
>>> util_color(graph, max_colors, colored_vertices, index)
True
>>> max_colors = 2
>>> util_color(graph, max_colors, colored_vertices, index)
False
"""
# Base Case: If all vertices have been assigned a color, we have a valid solution
if index == len(graph):
return True
# 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 if no solution found with the current assignment
colored_vertices[index] = -1
return False # Return False if no valid coloring is possible
def color(graph: list[list[int]], max_colors: int) -> list[int]:
"""
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]
>>> 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 # The successful color assignment
return [] # No valid coloring is possible