Checking if a Graph is Bipartite
Recall, that a graph $g = (V, E)$ is bipartite if its node set $V$ can be partitioned into two disjoint and non-empty sets $V1$ and $V2$ such that every edge in the graph connects a node in $V1$ and a node in $V2$. When this condition holds, we call the pair $(V1, V2)$ a bipartition of the node set $V$ of $g$.
We have seen in lecture and the handouts a brute-force algorithm for determining if a graph is bipartite. The pseudo-code for such an algorithm is:
We know how to implement such an algorithm in Python. One such implementation is:
def is_bipartite(g):
"""
Check if graph, g, is bipartite.
Arguments:
g -- undirected graph
Returns:
True if g is bipartite, False otherwise.
"""
V = set(g.keys())
half = len(V) / 2
# Check all possible partitions, where each partition has at least one node and no node
# is in both partitions.
for i in range(1, half+1):
for candidate in itertools.combinations(V, i):
# Create two candidate sets, V1 and V2
V1 = set(candidate)
V2 = V - V1
# Check all edges in the graph to ensure they don't connect nodes in the same set
bipartite = True
for u in g:
for v in g[u]:
edge = set([u, v])
if edge.issubset(V1) or edge.issubset(V2):
# Found an edge connecting nodes in same set, stop checking this partition
bipartite = False
break
if not bipartite:
# Already know this partition is not a bipartition, stop checking it
break
if bipartite:
# The current partition is a bipartition, the graph is bipartite
return True
# No bipartition was found, the graph is not bipartite
return False
Transform and Conquer
We know that the brute-force algorithm will work, but it is incredibly inefficient. Have we seen any other algorithms that might help us to determine if a graph is bipartite?
As with all transform and conquer solutions, in order to answer "yes", we must also answer the following questions:
- How can we transform the "is bipartite" problem into another problem?
- How do we solve that problem?
- How do we interpret the results of that problem to solve the "is bipartite" problem?
Does this transformation help us? Why or why not?
Let's Be More Clever (a.k.a. finally - I don't have to use brute force in lab!)
How can we solve the "is bipartite" problem (and/or the transformed problem) in a more clever way? Do we need to exhaustively search all partitions to determine if a graph is bipartite? What can we do instead?
Exercise 1 - Algorithm
Come up with an English-language description of an algorithm to solve the "is bipartite" problem that is better than brute force.
Exercise 2 - Correctness
Is your algorithm correct? How do you convince yourself and others of this?
Exercise 3 - Pseudo-code
Develop pseudo-code for your algorithm to determine whether a graph is bipartite.
Exercise 4 - Efficiency
Analyze the running time of your algorithm from Exercise 3 using big-O notation. Is it better than brute-force?
Exercise 5 - Implementation
Implement your algorithm developed in exercise 3. Use your algorithm to assert that:
g1 = { 'A' : set(['C', 'D', 'E']),
'B' : set(['C', 'D']),
'C' : set(['A', 'B']),
'D' : set(['A', 'B']),
'E' : set(['A']) }
is bipartite.
That
g2 = { 'A' : set(['C', 'D', 'E', 'B']),
'B' : set(['C', 'D', 'A']),
'C' : set(['A', 'B']),
'D' : set(['A', 'B']),
'E' : set(['A']) }
is not bipartite.
and that
g3 = { 'A' : set(['C', 'D', 'E']),
'B' : set(['C', 'D']),
'C' : set(['A', 'B']),
'D' : set(['A', 'B']),
'E' : set(['A']),
'F' : set(['Z', 'X']),
'X' : set(['F', 'Z']),
'Z' : set(['F', 'X']),}
is not bipartite.
Exercise 6 - Running Time
Compare the running time of your new algorithm with the brute-force algorithm given above. Does the running time of your algorithm match your big-O analysis from Exercise 4?
Submit your code
At the end of lab, submit your group's Python code here.