mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-10-06 05:39:30 +00:00
Compare commits
11 Commits
0e3e7030ac
...
7af8962b0b
Author | SHA1 | Date | |
---|---|---|---|
|
7af8962b0b | ||
|
9a572dec2b | ||
|
156f8fe5fd | ||
|
26321da52f | ||
|
7fafb02c4a | ||
|
42c32e7c4e | ||
|
9da28b0ccc | ||
|
8fae9d55a8 | ||
|
d38b935106 | ||
|
92449bfb32 | ||
|
4de67b014c |
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Graph Coloring also called "m coloring problem"
|
Graph Coloring (also called the "m coloring problem") is the problem of
|
||||||
consists of coloring a given graph with at most m colors
|
assigning at most 'm' colors to the vertices of a graph such that
|
||||||
such that no adjacent vertices are assigned the same color
|
no two adjacent vertices share the same color.
|
||||||
|
|
||||||
Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring
|
Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring
|
||||||
"""
|
"""
|
||||||
|
@ -11,13 +11,31 @@ def valid_coloring(
|
||||||
neighbours: list[int], colored_vertices: list[int], color: int
|
neighbours: list[int], colored_vertices: list[int], color: int
|
||||||
) -> bool:
|
) -> 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
|
For each neighbour check if the coloring constraint is satisfied
|
||||||
If any of the neighbours fail the constraint return False
|
If any of the neighbours fail the constraint return False
|
||||||
If all neighbours validate the constraint return True
|
If all neighbours validate the constraint return True
|
||||||
|
|
||||||
>>> neighbours = [0,1,0,1,0]
|
Parameters:
|
||||||
>>> colored_vertices = [0, 2, 1, 2, 0]
|
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
|
>>> color = 1
|
||||||
>>> valid_coloring(neighbours, colored_vertices, color)
|
>>> valid_coloring(neighbours, colored_vertices, color)
|
||||||
True
|
True
|
||||||
|
@ -25,8 +43,14 @@ def valid_coloring(
|
||||||
>>> color = 2
|
>>> color = 2
|
||||||
>>> valid_coloring(neighbours, colored_vertices, color)
|
>>> valid_coloring(neighbours, colored_vertices, color)
|
||||||
False
|
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(
|
return not any(
|
||||||
neighbour == 1 and colored_vertices[i] == color
|
neighbour == 1 and colored_vertices[i] == color
|
||||||
for i, neighbour in enumerate(neighbours)
|
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
|
graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Pseudo-Code
|
Recursive function to try and color the graph using backtracking.
|
||||||
|
|
||||||
Base Case:
|
Base Case:
|
||||||
1. Check if coloring is complete
|
1. Check if coloring is complete
|
||||||
|
@ -51,6 +75,20 @@ def util_color(
|
||||||
2.4. if current coloring leads to a solution return
|
2.4. if current coloring leads to a solution return
|
||||||
2.5. Uncolor given vertex
|
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],
|
>>> graph = [[0, 1, 0, 0, 0],
|
||||||
... [1, 0, 1, 0, 1],
|
... [1, 0, 1, 0, 1],
|
||||||
... [0, 1, 0, 1, 0],
|
... [0, 1, 0, 1, 0],
|
||||||
|
@ -67,36 +105,47 @@ def util_color(
|
||||||
>>> util_color(graph, max_colors, colored_vertices, index)
|
>>> util_color(graph, max_colors, colored_vertices, index)
|
||||||
False
|
False
|
||||||
"""
|
"""
|
||||||
|
# Base Case: If all vertices have been assigned a color, we have a valid solution
|
||||||
# Base Case
|
|
||||||
if index == len(graph):
|
if index == len(graph):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Recursive Step
|
# Try each color for the current vertex
|
||||||
for i in range(max_colors):
|
for color in range(max_colors):
|
||||||
if valid_coloring(graph[index], colored_vertices, i):
|
# Check if it's valid to color the current vertex with 'color'
|
||||||
# Color current vertex
|
if valid_coloring(graph[index], colored_vertices, color):
|
||||||
colored_vertices[index] = i
|
colored_vertices[index] = color # Assign color
|
||||||
# Validate coloring
|
# Recur to color the rest of the vertices
|
||||||
if util_color(graph, max_colors, colored_vertices, index + 1):
|
if util_color(graph, max_colors, colored_vertices, index + 1):
|
||||||
return True
|
return True
|
||||||
# Backtrack
|
# Backtrack if no solution found with the current assignment
|
||||||
colored_vertices[index] = -1
|
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]:
|
def color(graph: list[list[int]], max_colors: int) -> list[int]:
|
||||||
"""
|
"""
|
||||||
Wrapper function to call subroutine called util_color
|
Attempt to color the graph with at most max_colors colors such that no two adjacent
|
||||||
which will either return True or False.
|
vertices have the same color.
|
||||||
If True is returned colored_vertices list is filled with correct colorings
|
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],
|
>>> graph = [[0, 1, 0, 0, 0],
|
||||||
... [1, 0, 1, 0, 1],
|
... [1, 0, 1, 0, 1],
|
||||||
... [0, 1, 0, 1, 0],
|
... [0, 1, 0, 1, 0],
|
||||||
... [0, 1, 1, 0, 0],
|
... [0, 1, 1, 0, 0],
|
||||||
... [0, 1, 0, 0, 0]]
|
... [0, 1, 0, 0, 0]]
|
||||||
|
|
||||||
>>> max_colors = 3
|
>>> max_colors = 3
|
||||||
>>> color(graph, max_colors)
|
>>> color(graph, max_colors)
|
||||||
[0, 1, 0, 2, 0]
|
[0, 1, 0, 2, 0]
|
||||||
|
@ -104,10 +153,26 @@ def color(graph: list[list[int]], max_colors: int) -> list[int]:
|
||||||
>>> max_colors = 2
|
>>> max_colors = 2
|
||||||
>>> color(graph, max_colors)
|
>>> 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)
|
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):
|
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
|
||||||
|
|
|
@ -7,6 +7,8 @@ the Binet's formula function because the Binet formula function uses floats
|
||||||
|
|
||||||
NOTE 2: the Binet's formula function is much more limited in the size of inputs
|
NOTE 2: the Binet's formula function is much more limited in the size of inputs
|
||||||
that it can handle due to the size limitations of Python floats
|
that it can handle due to the size limitations of Python floats
|
||||||
|
NOTE 3: the matrix function is the fastest and most memory efficient for large n
|
||||||
|
|
||||||
|
|
||||||
See benchmark numbers in __main__ for performance comparisons/
|
See benchmark numbers in __main__ for performance comparisons/
|
||||||
https://en.wikipedia.org/wiki/Fibonacci_number for more information
|
https://en.wikipedia.org/wiki/Fibonacci_number for more information
|
||||||
|
@ -17,6 +19,9 @@ from collections.abc import Iterator
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
|
|
||||||
def time_func(func, *args, **kwargs):
|
def time_func(func, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -230,6 +235,88 @@ def fib_binet(n: int) -> list[int]:
|
||||||
return [round(phi**i / sqrt_5) for i in range(n + 1)]
|
return [round(phi**i / sqrt_5) for i in range(n + 1)]
|
||||||
|
|
||||||
|
|
||||||
|
def matrix_pow_np(m: ndarray, power: int) -> ndarray:
|
||||||
|
"""
|
||||||
|
Raises a matrix to the power of 'power' using binary exponentiation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
m: Matrix as a numpy array.
|
||||||
|
power: The power to which the matrix is to be raised.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The matrix raised to the power.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If power is negative.
|
||||||
|
|
||||||
|
>>> m = np.array([[1, 1], [1, 0]], dtype=int)
|
||||||
|
>>> matrix_pow_np(m, 0) # Identity matrix when raised to the power of 0
|
||||||
|
array([[1, 0],
|
||||||
|
[0, 1]])
|
||||||
|
|
||||||
|
>>> matrix_pow_np(m, 1) # Same matrix when raised to the power of 1
|
||||||
|
array([[1, 1],
|
||||||
|
[1, 0]])
|
||||||
|
|
||||||
|
>>> matrix_pow_np(m, 5)
|
||||||
|
array([[8, 5],
|
||||||
|
[5, 3]])
|
||||||
|
|
||||||
|
>>> matrix_pow_np(m, -1)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: power is negative
|
||||||
|
"""
|
||||||
|
result = np.array([[1, 0], [0, 1]], dtype=int) # Identity Matrix
|
||||||
|
base = m
|
||||||
|
if power < 0: # Negative power is not allowed
|
||||||
|
raise ValueError("power is negative")
|
||||||
|
while power:
|
||||||
|
if power % 2 == 1:
|
||||||
|
result = np.dot(result, base)
|
||||||
|
base = np.dot(base, base)
|
||||||
|
power //= 2
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def fib_matrix_np(n: int) -> int:
|
||||||
|
"""
|
||||||
|
Calculates the n-th Fibonacci number using matrix exponentiation.
|
||||||
|
https://www.nayuki.io/page/fast-fibonacci-algorithms#:~:text=
|
||||||
|
Summary:%20The%20two%20fast%20Fibonacci%20algorithms%20are%20matrix
|
||||||
|
|
||||||
|
Args:
|
||||||
|
n: Fibonacci sequence index
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The n-th Fibonacci number.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If n is negative.
|
||||||
|
|
||||||
|
>>> fib_matrix_np(0)
|
||||||
|
0
|
||||||
|
>>> fib_matrix_np(1)
|
||||||
|
1
|
||||||
|
>>> fib_matrix_np(5)
|
||||||
|
5
|
||||||
|
>>> fib_matrix_np(10)
|
||||||
|
55
|
||||||
|
>>> fib_matrix_np(-1)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: n is negative
|
||||||
|
"""
|
||||||
|
if n < 0:
|
||||||
|
raise ValueError("n is negative")
|
||||||
|
if n == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
m = np.array([[1, 1], [1, 0]], dtype=int)
|
||||||
|
result = matrix_pow_np(m, n - 1)
|
||||||
|
return int(result[0, 0])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from doctest import testmod
|
from doctest import testmod
|
||||||
|
|
||||||
|
@ -242,3 +329,4 @@ if __name__ == "__main__":
|
||||||
time_func(fib_memoization, num) # 0.0100 ms
|
time_func(fib_memoization, num) # 0.0100 ms
|
||||||
time_func(fib_recursive_cached, num) # 0.0153 ms
|
time_func(fib_recursive_cached, num) # 0.0153 ms
|
||||||
time_func(fib_recursive, num) # 257.0910 ms
|
time_func(fib_recursive, num) # 257.0910 ms
|
||||||
|
time_func(fib_matrix_np, num) # 0.0000 ms
|
||||||
|
|
Loading…
Reference in New Issue
Block a user