How to tell if a graph is bipartite
A bipartite graph is one whose vertices can be split into two disjoint sets (U) and (V) such that every edge connects a vertex in (U) to a vertex in (V). No edge joins two vertices that belong to the same set. That's why determining whether a given graph satisfies this property is a fundamental problem in graph theory with applications ranging from scheduling and network design to coding theory and chemistry. Below we explore several reliable techniques, walk through concrete examples, and highlight common mistakes to avoid.
Introduction
When faced with a graph—whether presented as an adjacency list, an adjacency matrix, or a drawing—you need a systematic way to decide if it is bipartite. Think about it: the most intuitive approach is to try to 2‑color the vertices: assign one of two colors (say, red and blue) so that every edge links vertices of different colors. If you succeed, the graph is bipartite; if you encounter a conflict, it is not. This idea underlies the algorithms described in the following sections Took long enough..
What Makes a Graph Bipartite?
A graph (G = (V, E)) is bipartite iff it contains no odd‑length cycle.
Proof sketch:
- If a graph is bipartite, any walk that starts and ends in the same part must have even length, because each step flips the part. On the flip side, hence every cycle is even. - Conversely, if a graph has no odd cycle, you can pick an arbitrary vertex, color it red, and propagate colors along any path; the absence of odd cycles guarantees that you never assign two conflicting colors to the same vertex.
Thus, detecting an odd cycle is equivalent to testing bipartiteness.
Method 1: BFS/DFS 2‑Coloring
The classic algorithm performs a breadth‑first (or depth‑first) search, coloring each newly discovered vertex with the opposite color of its parent.
Step‑by‑step procedure
- Initialize all vertices as uncolored (e.g., color = ‑1).
- For each vertex (v) (to handle disconnected graphs):
- If (v) is uncolored, assign it color 0 (red) and push it onto a queue/stack.
- While the queue/stack is not empty:
- Pop a vertex (x).
- For each neighbor (y) of (x):
- If (y) is uncolored, set
color[y] = 1 - color[x]and enqueue (y). - If (y) already has a color and
color[y] == color[x], conflict → graph is not bipartite.
- If (y) is uncolored, set
- If the search finishes without conflict, the graph is bipartite.
Pseudocode (BFS version)
function isBipartite(G):
color = array[|V|] filled with -1
for each vertex v in V:
if color[v] == -1:
color[v] = 0
queue = [v]
while queue not empty:
x = queue.pop()
for each y in neighbors(x):
if color[y] == -1:
color[y] = 1 - color[x]
queue.push(y)
else if color[y] == color[x]:
return false // odd cycle detected
return true
Complexity
- Time: (O(|V| + |E|)) – each vertex and edge is processed once.
- Space: (O(|V|)) for the color array and the queue/stack.
Why it works
The algorithm guarantees that any two vertices connected by an edge receive opposite colors. If a conflict appears, it means we have found a path from a vertex back to itself with an odd number of edges—exactly an odd cycle That's the part that actually makes a difference..
Method 2: DFS‑Based Odd‑Cycle Detection
A depth‑first search can also be used to detect odd cycles by keeping track of the depth (distance from the start vertex) of each node.
Procedure
- Run DFS from any uncolored vertex, assigning a depth (d[v]) (the number of edges from the root).
- For each tree edge ((u, v)) (where (v) is first discovered), set (d[v] = d[u] + 1).
- For each back edge ((u, v)) that connects a vertex to an already visited ancestor, compute the cycle length:
[ \text{length} = d[u] - d[v] + 1 ] If this length is odd, the graph is not bipartite. - If no odd‑length back edge is found in any component, the graph is bipartite.
Advantages
- Works well when the graph is already stored in a recursive DFS framework (e.g., for topological sorting).
- Detects the offending cycle directly, which can be useful for debugging.
Complexity
Same as BFS: (O(|V| + |E|)) time, (O(|V|)) space.
Method 3: Union‑Find with Parity (Disjoint Set Union)
This method treats the bipartiteness condition as a set of constraints: each edge forces its endpoints to belong to opposite parts. By augmenting a standard Union‑Find (DSU) structure with parity information, we can process edges in almost‑constant amortized time.
Data stored per element
parent[x]: representative of the set containing (x).rank[x](or size): for union by rank/size.parity[x]: 0 if (x) has the same color as its set’s representative, 1 if opposite.
Operations
- Find(x): returns the root of (x) while also updating
parity[x]to reflect the parity between (x) and the root (path compression). - Union(u, v): we need to enforce that (u) and (v) have opposite colors.
- Find roots (ru = find(u)), (rv = find(v)) with their parities (pu), (pv).
- If (ru == rv): check whether
pu == pv. If they are equal, then (u) and (v)