Improve comments, add doctests to coloring.py

This commit is contained in:
Hardik Pawar 2024-09-30 20:35:42 +05:30
parent a9ca110d6b
commit 4de67b014c

View File

@ -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