Consider the code below. Suppose the graph in question has N nodes with at most D neighbors for each node, and D+1 colors are available for coloring the nodes such that no two nodes connected with an edge have the same color assigned to them. I reckon the complexity of the code below is O(N*D) because for each of the N nodes we loop through the at most D neighbors of that node to populate the set illegal_colors, and then iterate through colors list that comprises D+1 colors. But the complexity given is O(N+M) where M is the number of edges. What am I doing wrong here?
def color_graph(graph, colors):
for node in graph:
if node in node.neighbors:
raise Exception('Legal coloring impossible for node with loop: %s' %
node.label)
# Get the node's neighbors' colors, as a set so we
# can check if a color is illegal in constant time
illegal_colors = set([
neighbor.color
for neighbor in node.neighbors
if neighbor.color
])
# Assign the first legal color
for color in colors:
if color not in illegal_colors:
node.color = color
break
The number of edges M
, the maximum degree D
and the number of nodes N
satisfy the inequality:
M <= N * D / 2
.
Therefore O(N+M)
is included in O(N*(D+1))
.
In your algorithm, you loop over every neighbour of every node. The exact complexity of that is not N*D
, but d1 + d2 + d3 + ... + dN
where di
is the degree of node i
. This sum is equal to 2*M
, which is at most N*D
but might be less.
Therefore the complexity of your algorithm is O(N+M)
. Hence it is also O(N*(D+1))
. Note that O(N*(D+1)) = O(N*D)
under the assumption D >= 1
.
Saying your algorithm runs in O(N+M)
is slightly more precise than saying it runs in O(N*D)
. If most nodes have a lot fewer than D
neighbours, then M+N
might be much smaller than N*D
.
Also note that O(M+N) = O(M)
under the assumption that every node has at least one neighbour.