180 lines
6.5 KiB
Python
180 lines
6.5 KiB
Python
import itertools
|
|
|
|
__all__ = ['greedy_coloring_with_interchange']
|
|
|
|
|
|
class Node(object):
|
|
|
|
__slots__ = ['node_id', 'color', 'adj_list', 'adj_color']
|
|
|
|
def __init__(self, node_id, n):
|
|
self.node_id = node_id
|
|
self.color = -1
|
|
self.adj_list = None
|
|
self.adj_color = [None for _ in range(n)]
|
|
|
|
def __repr__(self):
|
|
return "Node_id: {0}, Color: {1}, Adj_list: ({2}), \
|
|
adj_color: ({3})".format(
|
|
self.node_id, self.color, self.adj_list, self.adj_color)
|
|
|
|
def assign_color(self, adj_entry, color):
|
|
adj_entry.col_prev = None
|
|
adj_entry.col_next = self.adj_color[color]
|
|
self.adj_color[color] = adj_entry
|
|
if adj_entry.col_next is not None:
|
|
adj_entry.col_next.col_prev = adj_entry
|
|
|
|
def clear_color(self, adj_entry, color):
|
|
if adj_entry.col_prev is None:
|
|
self.adj_color[color] = adj_entry.col_next
|
|
else:
|
|
adj_entry.col_prev.col_next = adj_entry.col_next
|
|
if adj_entry.col_next is not None:
|
|
adj_entry.col_next.col_prev = adj_entry.col_prev
|
|
|
|
def iter_neighbors(self):
|
|
adj_node = self.adj_list
|
|
while adj_node is not None:
|
|
yield adj_node
|
|
adj_node = adj_node.next
|
|
|
|
def iter_neighbors_color(self, color):
|
|
adj_color_node = self.adj_color[color]
|
|
while adj_color_node is not None:
|
|
yield adj_color_node.node_id
|
|
adj_color_node = adj_color_node.col_next
|
|
|
|
|
|
class AdjEntry(object):
|
|
|
|
__slots__ = ['node_id', 'next', 'mate', 'col_next', 'col_prev']
|
|
|
|
def __init__(self, node_id):
|
|
self.node_id = node_id
|
|
self.next = None
|
|
self.mate = None
|
|
self.col_next = None
|
|
self.col_prev = None
|
|
|
|
def __repr__(self):
|
|
return "Node_id: {0}, Next: ({1}), Mate: ({2}), \
|
|
col_next: ({3}), col_prev: ({4})".format(
|
|
self.node_id,
|
|
self.next,
|
|
self.mate.node_id,
|
|
None if self.col_next is None else self.col_next.node_id,
|
|
None if self.col_prev is None else self.col_prev.node_id
|
|
)
|
|
|
|
|
|
def greedy_coloring_with_interchange(original_graph, nodes):
|
|
"""
|
|
This procedure is an adaption of the algorithm described by [1]_,
|
|
and is an implementation of coloring with interchange. Please be
|
|
advised, that the datastructures used are rather complex because
|
|
they are optimized to minimize the time spent identifying
|
|
subcomponents of the graph, which are possible candidates for color
|
|
interchange.
|
|
|
|
References
|
|
----------
|
|
.. [1] Maciej M. Syslo, Marsingh Deo, Janusz S. Kowalik,
|
|
Discrete Optimization Algorithms with Pascal Programs, 415-424, 1983.
|
|
ISBN 0-486-45353-7.
|
|
"""
|
|
n = len(original_graph)
|
|
|
|
graph = {node_id: Node(node_id, n) for node_id in original_graph}
|
|
|
|
for (node1, node2) in original_graph.edges():
|
|
adj_entry1 = AdjEntry(node2)
|
|
adj_entry2 = AdjEntry(node1)
|
|
adj_entry1.mate = adj_entry2
|
|
adj_entry2.mate = adj_entry1
|
|
node1_head = graph[node1].adj_list
|
|
adj_entry1.next = node1_head
|
|
graph[node1].adj_list = adj_entry1
|
|
node2_head = graph[node2].adj_list
|
|
adj_entry2.next = node2_head
|
|
graph[node2].adj_list = adj_entry2
|
|
|
|
k = 0
|
|
for node in nodes:
|
|
# Find the smallest possible, unused color
|
|
neighbors = graph[node].iter_neighbors()
|
|
col_used = {graph[adj_node.node_id].color for adj_node in neighbors}
|
|
col_used.discard(-1)
|
|
k1 = next(itertools.dropwhile(
|
|
lambda x: x in col_used, itertools.count()))
|
|
|
|
# k1 is now the lowest available color
|
|
if k1 > k:
|
|
connected = True
|
|
visited = set()
|
|
col1 = -1
|
|
col2 = -1
|
|
while connected and col1 < k:
|
|
col1 += 1
|
|
neighbor_cols = (
|
|
graph[node].iter_neighbors_color(col1))
|
|
col1_adj = [it for it in neighbor_cols]
|
|
|
|
col2 = col1
|
|
while connected and col2 < k:
|
|
col2 += 1
|
|
visited = set(col1_adj)
|
|
frontier = list(col1_adj)
|
|
i = 0
|
|
while i < len(frontier):
|
|
search_node = frontier[i]
|
|
i += 1
|
|
col_opp = (
|
|
col2 if graph[search_node].color == col1 else col1)
|
|
neighbor_cols = (
|
|
graph[search_node].iter_neighbors_color(col_opp))
|
|
|
|
for neighbor in neighbor_cols:
|
|
if neighbor not in visited:
|
|
visited.add(neighbor)
|
|
frontier.append(neighbor)
|
|
|
|
# Search if node is not adj to any col2 vertex
|
|
connected = len(visited.intersection(
|
|
graph[node].iter_neighbors_color(col2))) > 0
|
|
|
|
# If connected is false then we can swap !!!
|
|
if not connected:
|
|
# Update all the nodes in the component
|
|
for search_node in visited:
|
|
graph[search_node].color = (
|
|
col2 if graph[search_node].color == col1 else col1)
|
|
col2_adj = graph[search_node].adj_color[col2]
|
|
graph[search_node].adj_color[col2] = (
|
|
graph[search_node].adj_color[col1])
|
|
graph[search_node].adj_color[col1] = col2_adj
|
|
|
|
# Update all the neighboring nodes
|
|
for search_node in visited:
|
|
col = graph[search_node].color
|
|
col_opp = col1 if col == col2 else col2
|
|
for adj_node in graph[search_node].iter_neighbors():
|
|
if graph[adj_node.node_id].color != col_opp:
|
|
# Direct reference to entry
|
|
adj_mate = adj_node.mate
|
|
graph[adj_node.node_id].clear_color(
|
|
adj_mate, col_opp)
|
|
graph[adj_node.node_id].assign_color(adj_mate, col)
|
|
k1 = col1
|
|
|
|
# We can color this node color k1
|
|
graph[node].color = k1
|
|
k = max(k1, k)
|
|
|
|
# Update the neighbors of this node
|
|
for adj_node in graph[node].iter_neighbors():
|
|
adj_mate = adj_node.mate
|
|
graph[adj_node.node_id].assign_color(adj_mate, k1)
|
|
|
|
return {node.node_id: node.color for node in graph.values()}
|