What is a Bipartite Graph?
A Bipartite Graph (or bigraph) is a graph whose vertices can be divided into two disjoint and independent sets $U$ and $V$ such that every edge connects a vertex in $U$ to one in $V$. In other words, no two vertices within the same set are adjacent. It is 2-colorable and contains no odd-length cycles.
Explanation
- Bipartite graphs are fundamental in computer science, especially for modeling relationship networks between two different classes of objects. A key mathematical property is that a graph is bipartite if and only if it contains no odd-length cycles.
Real-World Analogy
- Job Applicants & Job Openings (Matching): An applicant can apply for jobs, but applicants do not connect to other applicants, and job openings do not connect to other job openings.
- Actors & Movies (Bipartite Network): An actor acts in a movie. An actor does not connect directly to another actor, and a movie does not connect directly to another movie.
- Users & Products (Recommendation Systems): Users rate products. Connections only exist between users and products, never between users themselves or products themselves.
How It Works
Core Mechanics
- Checking if a graph is bipartite is equivalent to checking if it is 2-colorable.
- We traverse the graph using Breadth-First Search (BFS) or Depth-First Search (DFS) and color each uncolored node with a starting color (e.g.,
0). - For each visited node, color all its adjacent unvisited neighbors with the opposite color (
1 - current_color). - If an adjacent neighbor is already colored and has the same color as the current node, the graph contains an odd-length cycle and is not bipartite.
- Since graphs can be disconnected, we must run this coloring check for all components by looping over all vertices.
BFS vs DFS Bipartiteness Check
- Both BFS and DFS can check bipartiteness in $O(V + E)$ time.
- BFS Approach: Explores level-by-level, making it intuitive for layering colors.
- DFS Approach: Explores paths deeply and backtracks, which is equally efficient but uses call stack memory instead of a queue.
Visual Walkthrough
1. Bipartite Graph (Square: 0-1-2-3-0)
0(Color A) ----- 1(Color B)
| |
| |
3(Color B) ----- 2(Color A)
- Color Assignment Process:
- Start BFS at
0, color it A. - Neighbors of
0are1and3. Color both B. - Dequeue
1. Neighbor2is uncolored; color it A. Neighbor0is already colored A (no conflict). - Dequeue
3. Neighbor2is already colored A (no conflict). Neighbor0is already colored A (no conflict). - Dequeue
2. All neighbors colored. BFS ends. - The partition is $U = {0, 2}$ and $V = {1, 3}$. Valid!
- Start BFS at
2. Non-Bipartite Graph (Triangle: 0-1-2-0)
0(Color A) ----- 1(Color B)
\ /
\ /
2(Color B / Conflict!)
- Color Assignment Process:
- Start BFS at
0, color it A. - Neighbors of
0are1and2. Color both B. - Dequeue
1. Neighbor2is already colored B. But1is also colored B! - Conflict: Neighbor
2has the same color as current node1. Bipartiteness fails because an odd cycle ($0 \to 1 \to 2 \to 0$) of length 3 exists.
- Start BFS at
Time & Space Complexity
| Algorithm / Representation | Time Complexity | Space Complexity |
|---|---|---|
| BFS Bipartite Check (Adjacency List) | $O(V + E)$ | $O(V)$ |
| DFS Bipartite Check (Adjacency List) | $O(V + E)$ | $O(V)$ |
- Note: $V$ is the number of vertices, and $E$ is the number of edges. Space is used for the color tracking array and the BFS queue or DFS recursion stack.
Implementation
from collections import deque
def is_bipartite(V, adj):
"""
Determines if a graph represented as an adjacency list is bipartite.
Handles disconnected graphs.
Colors:
-1 : Uncolored
0 : Color A
1 : Color B
"""
color = [-1] * V
for start in range(V):
if color[start] == -1:
# Start BFS for this component
color[start] = 0
queue = deque([start])
while queue:
u = queue.popleft()
for v in adj[u]:
if color[v] == -1:
# Color neighbor with the opposite color
color[v] = 1 - color[u]
queue.append(v)
elif color[v] == color[u]:
# Conflict detected: adjacent nodes share the same color
return False
return True
# Example Usage
if __name__ == "__main__":
# Bipartite Graph (Square: 0-1-2-3-0)
V1 = 4
adj1 = [
[1, 3], # 0
[0, 2], # 1
[1, 3], # 2
[0, 2] # 3
]
print(f"Graph 1 Bipartite? {is_bipartite(V1, adj1)}") # Output: True
# Non-Bipartite Graph (Triangle: 0-1-2-0)
V2 = 3
adj2 = [
[1, 2], # 0
[0, 2], # 1
[0, 1] # 2
]
print(f"Graph 2 Bipartite? {is_bipartite(V2, adj2)}") # Output: False#include <iostream>
#include <vector>
#include <queue>
class BipartiteChecker {
public:
/**
* Determines if a graph is bipartite.
* Handles disconnected graphs.
*
* Colors:
* -1 : Uncolored
* 0 : Color A
* 1 : Color B
*/
static bool isBipartite(int V, const std::vector<std::vector<int>>& adj) {
std::vector<int> color(V, -1);
for (int start = 0; start < V; ++start) {
if (color[start] == -1) {
// Start BFS for this component
color[start] = 0;
std::queue<int> q;
q.push(start);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : adj[u]) {
if (color[v] == -1) {
// Color neighbor with the opposite color
color[v] = 1 - color[u];
q.push(v);
} else if (color[v] == color[u]) {
// Conflict detected: adjacent nodes share the same color
return false;
}
}
}
}
}
return true;
}
};
int main() {
// Bipartite Graph (Square: 0-1-2-3-0)
int V1 = 4;
std::vector<std::vector<int>> adj1(V1);
adj1[0] = {1, 3};
adj1[1] = {0, 2};
adj1[2] = {1, 3};
adj1[3] = {0, 2};
std::cout << "Graph 1 Bipartite? "
<< (BipartiteChecker::isBipartite(V1, adj1) ? "Yes" : "No")
<< std::endl; // Output: Yes
// Non-Bipartite Graph (Triangle: 0-1-2-0)
int V2 = 3;
std::vector<std::vector<int>> adj2(V2);
adj2[0] = {1, 2};
adj2[1] = {0, 2};
adj2[2] = {0, 1};
std::cout << "Graph 2 Bipartite? "
<< (BipartiteChecker::isBipartite(V2, adj2) ? "Yes" : "No")
<< std::endl; // Output: No
return 0;
}
When to Use
✅ Use Bipartite Check When:
- You are solving the Maximum Bipartite Matching problem (e.g. assigning jobs to candidates, students to dorm rooms).
- You need to determine if a graph can be 2-colored (e.g. checking if register allocation is possible with only 2 registers).
- You are analyzing social networks or transaction networks to partition them into two distinct categories (e.g. buyers and sellers).
Variations & Related Concepts
- Complete Bipartite Graph ($K_{m,n}$): A special bipartite graph where every vertex of Set $U$ ($|U|=m$) is connected to every vertex of Set $V$ ($|V|=n$). It has a total of $m \times n$ edges.
- Graph Coloring: Coloring nodes such that no two adjacent nodes share the same color. A graph is bipartite if and only if its chromatic number is $\le 2$.
- Odd Cycle Detection: Finding if a graph has a cycle of odd length. A graph has an odd cycle if and only if it is NOT bipartite.
Key Takeaways
- A graph is bipartite if its vertices can be split into two independent sets with no intra-set edges.
- A graph is bipartite if and only if it does not contain any odd-length cycles.
- You can check bipartiteness in $O(V + E)$ time using BFS or DFS 2-coloring.
- Remember to loop over all vertices to handle disconnected graphs correctly.