|
1 | 1 | /** |
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) |
4 | 3 | * |
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) |
6 | 18 | * |
7 | 19 | * @author William Fiset, william.alexandre.fiset@gmail.com |
8 | 20 | */ |
9 | 21 | package com.williamfiset.algorithms.graphtheory.networkflow; |
10 | 22 |
|
11 | | -import com.williamfiset.algorithms.utils.graphutils.Utils; |
| 23 | +import java.util.ArrayList; |
12 | 24 | import java.util.List; |
13 | 25 |
|
14 | 26 | public class BipartiteGraphCheckAdjacencyList { |
15 | 27 |
|
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; |
17 | 34 | private int[] colors; |
18 | 35 | private boolean solved; |
19 | 36 | private boolean isBipartite; |
20 | | - private List<List<Integer>> graph; |
21 | | - |
22 | | - @SuppressWarnings("XorPower") |
23 | | - public static final int RED = 0b10, BLACK = (RED ^ 1); |
24 | 37 |
|
25 | 38 | public BipartiteGraphCheckAdjacencyList(List<List<Integer>> graph) { |
26 | 39 | if (graph == null) throw new IllegalArgumentException("Graph cannot be null."); |
27 | | - n = graph.size(); |
| 40 | + this.n = graph.size(); |
28 | 41 | this.graph = graph; |
29 | 42 | } |
30 | 43 |
|
31 | | - // Checks whether the input graph is bipartite. |
| 44 | + /** Returns true if the graph is bipartite (2-colorable). */ |
32 | 45 | public boolean isBipartite() { |
33 | 46 | if (!solved) solve(); |
34 | 47 | return isBipartite; |
35 | 48 | } |
36 | 49 |
|
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 | + */ |
40 | 54 | public int[] getTwoColoring() { |
41 | 55 | return isBipartite() ? colors : null; |
42 | 56 | } |
43 | 57 |
|
44 | 58 | private void solve() { |
45 | 59 | if (n <= 1) return; |
46 | | - |
47 | 60 | 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(); |
53 | 62 | solved = true; |
54 | 63 | } |
55 | 64 |
|
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 | + } |
68 | 70 |
|
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; |
73 | 77 | 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; |
80 | 79 | } |
| 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 | + } |
81 | 90 |
|
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); |
83 | 94 | } |
84 | 95 |
|
85 | | - /* Example usage */ |
| 96 | + // ==================== Main ==================== |
86 | 97 |
|
87 | 98 | public static void main(String[] args) { |
| 99 | + testSelfLoop(); |
| 100 | + testTwoNodes(); |
| 101 | + testTriangle(); |
| 102 | + testDisjoint(); |
| 103 | + testSquare(); |
| 104 | + testSquareWithDiagonal(); |
| 105 | + } |
88 | 106 |
|
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 | + } |
190 | 119 |
|
| 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 |
191 | 130 | } |
192 | 131 |
|
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 | + } |
195 | 148 |
|
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 | + } |
198 | 161 |
|
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 | + } |
201 | 179 |
|
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 |
204 | 198 | } |
205 | 199 | } |
0 commit comments