Skip to content

Commit 6e239c9

Browse files
williamfisetclaude
andauthored
Refactor BipartiteGraphCheck: add docs, remove Utils dep, add visuals (#1292)
* Refactor BipartiteGraphCheckAdjacencyList: add docs, remove Utils dep, add visuals Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Simplify colorGraph DFS, extract allNodesVisited, add UNVISITED constant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 20b7e29 commit 6e239c9

2 files changed

Lines changed: 182 additions & 205 deletions

File tree

Lines changed: 147 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,205 +1,199 @@
11
/**
2-
* This file shows you how to determine if a graph is bipartite or not. This can be achieved in
3-
* linear time by coloring the visited nodes.
2+
* Bipartite Graph Check — Adjacency List (DFS coloring)
43
*
5-
* <p>Time Complexity: O(V + E)
4+
* Determines if an undirected graph is bipartite (2-colorable) by attempting
5+
* to color it with two colors via DFS. A graph is bipartite if and only if
6+
* it contains no odd-length cycles.
7+
*
8+
* The algorithm starts a DFS from node 0, alternating colors RED and BLACK.
9+
* If a neighbor already has the same color as the current node, the graph
10+
* is not bipartite.
11+
*
12+
* Note: this implementation only checks the connected component containing
13+
* node 0. Disconnected graphs with multiple components are reported as
14+
* not bipartite.
15+
*
16+
* Time: O(V + E)
17+
* Space: O(V)
618
*
719
* @author William Fiset, william.alexandre.fiset@gmail.com
820
*/
921
package com.williamfiset.algorithms.graphtheory.networkflow;
1022

11-
import com.williamfiset.algorithms.utils.graphutils.Utils;
23+
import java.util.ArrayList;
1224
import java.util.List;
1325

1426
public class BipartiteGraphCheckAdjacencyList {
1527

16-
private int n;
28+
// Color constants. XOR with 0b01 toggles between RED and BLACK:
29+
// RED ^ 1 = BLACK, BLACK ^ 1 = RED.
30+
public static final int UNVISITED = 0, RED = 0b10, BLACK = 0b11;
31+
32+
private final int n;
33+
private final List<List<Integer>> graph;
1734
private int[] colors;
1835
private boolean solved;
1936
private boolean isBipartite;
20-
private List<List<Integer>> graph;
21-
22-
@SuppressWarnings("XorPower")
23-
public static final int RED = 0b10, BLACK = (RED ^ 1);
2437

2538
public BipartiteGraphCheckAdjacencyList(List<List<Integer>> graph) {
2639
if (graph == null) throw new IllegalArgumentException("Graph cannot be null.");
27-
n = graph.size();
40+
this.n = graph.size();
2841
this.graph = graph;
2942
}
3043

31-
// Checks whether the input graph is bipartite.
44+
/** Returns true if the graph is bipartite (2-colorable). */
3245
public boolean isBipartite() {
3346
if (!solved) solve();
3447
return isBipartite;
3548
}
3649

37-
// If the input graph is bipartite it has a two coloring which can be obtained
38-
// through this method. Each index in the returned array is either RED or BLACK
39-
// indicating which color node i was colored.
50+
/**
51+
* Returns the two-coloring array if the graph is bipartite, null otherwise.
52+
* Each entry is either RED or BLACK.
53+
*/
4054
public int[] getTwoColoring() {
4155
return isBipartite() ? colors : null;
4256
}
4357

4458
private void solve() {
4559
if (n <= 1) return;
46-
4760
colors = new int[n];
48-
int nodesVisited = colorGraph(0, RED);
49-
50-
// The graph is not bipartite. Either not all the nodes were visited or the
51-
// colorGraph method returned -1 meaning the graph is not 2-colorable.
52-
isBipartite = (nodesVisited == n);
61+
isBipartite = colorGraph(0, RED) && allNodesVisited();
5362
solved = true;
5463
}
5564

56-
// Do a depth first search coloring the nodes of the graph as we go.
57-
// This method returns the count of the number of nodes visited while
58-
// coloring the graph or -1 if this graph is not bipartite.
59-
private int colorGraph(int i, int color) {
60-
colors[i] = color;
61-
62-
// Toggles the color between RED and BLACK by exploiting the binary representation
63-
// of the constants and flipping the least significant bit on and off.
64-
int nextColor = (color ^ 1);
65-
66-
int visitCount = 1;
67-
List<Integer> edges = graph.get(i);
65+
private boolean allNodesVisited() {
66+
for (int c : colors)
67+
if (c == UNVISITED) return false;
68+
return true;
69+
}
6870

69-
for (int to : edges) {
70-
// Contradiction found. In a bipartite graph no two
71-
// nodes of the same color can be next to each other!
72-
if (colors[to] == color) return -1;
71+
/** DFS that colors nodes alternately. Returns false if a contradiction is found. */
72+
private boolean colorGraph(int i, int color) {
73+
colors[i] = color;
74+
int nextColor = color ^ 1;
75+
for (int to : graph.get(i)) {
76+
if (colors[to] == color) return false;
7377
if (colors[to] == nextColor) continue;
74-
75-
// If a contradiction is found propagate return -1
76-
// otherwise keep track of the number of visited nodes.
77-
int count = colorGraph(to, nextColor);
78-
if (count == -1) return -1;
79-
visitCount += count;
78+
if (!colorGraph(to, nextColor)) return false;
8079
}
80+
return true;
81+
}
82+
83+
/* Graph helpers */
84+
85+
public static List<List<Integer>> createGraph(int n) {
86+
List<List<Integer>> graph = new ArrayList<>(n);
87+
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
88+
return graph;
89+
}
8190

82-
return visitCount;
91+
public static void addUndirectedEdge(List<List<Integer>> graph, int from, int to) {
92+
graph.get(from).add(to);
93+
graph.get(to).add(from);
8394
}
8495

85-
/* Example usage */
96+
// ==================== Main ====================
8697

8798
public static void main(String[] args) {
99+
testSelfLoop();
100+
testTwoNodes();
101+
testTriangle();
102+
testDisjoint();
103+
testSquare();
104+
testSquareWithDiagonal();
105+
}
88106

89-
// Singleton (not bipartite)
90-
int n = 1;
91-
List<List<Integer>> graph = Utils.createEmptyAdjacencyList(n);
92-
Utils.addUndirectedEdge(graph, 0, 0);
93-
displayGraph(graph);
94-
95-
// Prints:
96-
// Graph has 1 node(s) and the following edges:
97-
// 0 -> 0
98-
// 0 -> 0
99-
// This graph is bipartite: false
100-
101-
// Two nodes one edge between them (bipartite)
102-
n = 2;
103-
graph = Utils.createEmptyAdjacencyList(n);
104-
Utils.addUndirectedEdge(graph, 0, 1);
105-
displayGraph(graph);
106-
107-
// Prints:
108-
// Graph has 2 node(s) and the following edges:
109-
// 0 -> 1
110-
// 1 -> 0
111-
// This graph is bipartite: true
112-
113-
// Triangle graph (not bipartite)
114-
n = 3;
115-
graph = Utils.createEmptyAdjacencyList(n);
116-
Utils.addUndirectedEdge(graph, 0, 1);
117-
Utils.addUndirectedEdge(graph, 1, 2);
118-
Utils.addUndirectedEdge(graph, 2, 0);
119-
displayGraph(graph);
120-
121-
// Prints:
122-
// Graph has 3 node(s) and the following edges:
123-
// 0 -> 1
124-
// 0 -> 2
125-
// 1 -> 0
126-
// 1 -> 2
127-
// 2 -> 1
128-
// 2 -> 0
129-
// This graph is bipartite: false
130-
131-
// Disjoint graph is bipartite connected components (altogether not bipartite)
132-
n = 4;
133-
graph = Utils.createEmptyAdjacencyList(n);
134-
Utils.addUndirectedEdge(graph, 0, 1);
135-
Utils.addUndirectedEdge(graph, 2, 3);
136-
displayGraph(graph);
137-
138-
// Prints:
139-
// Graph has 4 node(s) and the following edges:
140-
// 0 -> 1
141-
// 1 -> 0
142-
// 2 -> 3
143-
// 3 -> 2
144-
// This graph is bipartite: false
145-
146-
// Square graph (bipartite)
147-
n = 4;
148-
graph = Utils.createEmptyAdjacencyList(n);
149-
Utils.addUndirectedEdge(graph, 0, 1);
150-
Utils.addUndirectedEdge(graph, 1, 2);
151-
Utils.addUndirectedEdge(graph, 2, 3);
152-
Utils.addUndirectedEdge(graph, 3, 0);
153-
displayGraph(graph);
154-
155-
// Prints:
156-
// Graph has 4 node(s) and the following edges:
157-
// 0 -> 1
158-
// 0 -> 3
159-
// 1 -> 0
160-
// 1 -> 2
161-
// 2 -> 1
162-
// 2 -> 3
163-
// 3 -> 2
164-
// 3 -> 0
165-
// This graph is bipartite: true
166-
167-
// Square graph with additional edge (not bipartite)
168-
n = 4;
169-
graph = Utils.createEmptyAdjacencyList(n);
170-
Utils.addUndirectedEdge(graph, 0, 1);
171-
Utils.addUndirectedEdge(graph, 1, 2);
172-
Utils.addUndirectedEdge(graph, 2, 3);
173-
Utils.addUndirectedEdge(graph, 3, 0);
174-
Utils.addUndirectedEdge(graph, 0, 2);
175-
displayGraph(graph);
176-
177-
// Prints:
178-
// Graph has 4 node(s) and the following edges:
179-
// 0 -> 1
180-
// 0 -> 3
181-
// 0 -> 2
182-
// 1 -> 0
183-
// 1 -> 2
184-
// 2 -> 1
185-
// 2 -> 3
186-
// 2 -> 0
187-
// 3 -> 2
188-
// 3 -> 0
189-
// This graph is bipartite: false
107+
//
108+
// +-+
109+
// |0| (self-loop)
110+
// +-+
111+
//
112+
// Not bipartite: node 0 is adjacent to itself.
113+
//
114+
private static void testSelfLoop() {
115+
List<List<Integer>> g = createGraph(1);
116+
addUndirectedEdge(g, 0, 0);
117+
System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // false
118+
}
190119

120+
//
121+
// R --- B
122+
// 0 1
123+
//
124+
// Bipartite: {0=RED, 1=BLACK}
125+
//
126+
private static void testTwoNodes() {
127+
List<List<Integer>> g = createGraph(2);
128+
addUndirectedEdge(g, 0, 1);
129+
System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // true
191130
}
192131

193-
private static void displayGraph(List<List<Integer>> graph) {
194-
final int n = graph.size();
132+
//
133+
// R --- B
134+
// 0 1
135+
// \ /
136+
// 2
137+
// ? ← must be both R and B
138+
//
139+
// Not bipartite: odd cycle (0-1-2).
140+
//
141+
private static void testTriangle() {
142+
List<List<Integer>> g = createGraph(3);
143+
addUndirectedEdge(g, 0, 1);
144+
addUndirectedEdge(g, 1, 2);
145+
addUndirectedEdge(g, 2, 0);
146+
System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // false
147+
}
195148

196-
System.out.println("Graph has " + n + " node(s) and the following edges:");
197-
for (int f = 0; f < n; f++) for (int t : graph.get(f)) System.out.println(f + " -> " + t);
149+
//
150+
// R --- B ? --- ?
151+
// 0 1 2 3
152+
//
153+
// Not bipartite: nodes 2,3 unreachable from node 0.
154+
//
155+
private static void testDisjoint() {
156+
List<List<Integer>> g = createGraph(4);
157+
addUndirectedEdge(g, 0, 1);
158+
addUndirectedEdge(g, 2, 3);
159+
System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // false
160+
}
198161

199-
BipartiteGraphCheckAdjacencyList solver;
200-
solver = new BipartiteGraphCheckAdjacencyList(graph);
162+
//
163+
// R --- B
164+
// 0 1
165+
// | |
166+
// 3 2
167+
// B --- R
168+
//
169+
// Bipartite: {0=RED, 1=BLACK, 2=RED, 3=BLACK}
170+
//
171+
private static void testSquare() {
172+
List<List<Integer>> g = createGraph(4);
173+
addUndirectedEdge(g, 0, 1);
174+
addUndirectedEdge(g, 1, 2);
175+
addUndirectedEdge(g, 2, 3);
176+
addUndirectedEdge(g, 3, 0);
177+
System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // true
178+
}
201179

202-
System.out.println("This graph is bipartite: " + (solver.isBipartite()));
203-
System.out.println();
180+
//
181+
// R --- B
182+
// 0 1
183+
// | \ |
184+
// | \ |
185+
// 3 2
186+
// B ? ← must be both R and B
187+
//
188+
// Not bipartite: diagonal 0-2 creates odd cycle (0-1-2).
189+
//
190+
private static void testSquareWithDiagonal() {
191+
List<List<Integer>> g = createGraph(4);
192+
addUndirectedEdge(g, 0, 1);
193+
addUndirectedEdge(g, 1, 2);
194+
addUndirectedEdge(g, 2, 3);
195+
addUndirectedEdge(g, 3, 0);
196+
addUndirectedEdge(g, 0, 2);
197+
System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // false
204198
}
205199
}

0 commit comments

Comments
 (0)