Tutorials › Graph Theory (Undergraduate) › BFS Two-Coloring for Bipartiteness

Graph Traversal: BFS and DFS · Tutorial 732 of 799

BFS Two-Coloring for Bipartiteness

Use BFS to test bipartiteness, construct a valid two-coloring when one exists, and recognize a conflict without overwriting assigned colors.

Intermediate 9 min read

What You'll Learn

  • Define BFS two-coloring for finite, simple, undirected graphs.
  • Trace the queue and color assignments, including restarts in disconnected graphs.
  • Prove that a successful coloring gives a bipartition.
  • Prove that a same-color edge certifies that no bipartition exists.
  • Avoid the overwrite error that can conceal a conflict.
  • Bound the running time using an adjacency-list representation.

Two Colors Guided by Breadth-First Search

A graph is bipartite when its vertices can be divided into two parts so that every edge joins different parts. One way to find such a division is to assign two colors, requiring the endpoints of each edge to have opposite colors. Breadth-first search (BFS) gives a systematic order for making these assignments: when a vertex is reached, assign its uncolored neighbors the opposite color. The essential safeguard is to check a neighbor’s existing color before doing anything to it. If an already-colored neighbor has the same color as the vertex being processed, the algorithm reports failure rather than changing either assignment.

Throughout, \(G=(V,E)\) is a finite, simple, undirected graph. A proper two-coloring is a function \(c:V\to\{0,1\}\) such that \(c(u)\ne c(v)\) for every edge \(uv\in E\). Such a coloring gives a bipartition: its two parts are the vertices colored \(0\) and the vertices colored \(1\). Either part may be empty, so the definition also covers an edgeless graph.

Definition: BFS two-coloring is the following procedure. Initially every vertex is uncolored. Scan the vertices in a fixed order; whenever an uncolored vertex \(r\) is found, set \(c(r)=0\) and start a BFS queue with \(r\). When removing a vertex \(u\) from the queue, inspect each neighbor \(v\). If \(v\) is uncolored, set \(c(v)=1-c(u)\), record \(u\) as its BFS parent, and enqueue \(v\). If \(v\) is already colored, leave its color unchanged and report a conflict if \(c(v)=c(u)\). The procedure succeeds if all components are processed without a conflict.

The restart rule matters: a BFS started at one vertex reaches only its connected component. Starting a new search at each remaining uncolored vertex ensures that every component, including an isolated vertex, is handled. The parent edges form a BFS forest, with one root in each component.

Why the Search Reaches Every Vertex

Lemma: When BFS started at a root \(r\) finishes, every vertex in the connected component of \(r\) has been discovered.

Proof. Suppose instead that some vertex in the component of \(r\) remains undiscovered when the queue becomes empty. Since it is connected to \(r\), there is a path from \(r\) to that vertex. Along this path, take the first edge \(xy\), starting from \(r\), whose endpoint \(x\) was discovered and whose endpoint \(y\) was not. A discovered vertex is either waiting in the queue or has already been removed from it. In the first case, the search has not finished, because the queue is nonempty. In the second case, processing \(x\) inspected every neighbor, including \(y\), and would have discovered \(y\). Both cases contradict the assumption that the queue is empty while \(y\) is undiscovered. \(\square\)

Thus, after each restart, that BFS processes exactly the vertices of its root’s connected component. The component claim follows because BFS only follows edges, so it cannot reach a vertex outside the component, and the lemma shows that it reaches every vertex inside it.

Correctness of the Two-Coloring Test

Theorem: For a finite, simple, undirected graph, BFS two-coloring succeeds if and only if the graph is bipartite. If the procedure reports a same-color edge, then the graph has no proper two-coloring.

Proof. First suppose the procedure succeeds. Every vertex is colored, since BFS processes each component and the outer scan starts a search at each vertex that remains uncolored. Consider any edge \(uv\). When the earlier of \(u\) and \(v\) was processed, the other endpoint was either uncolored and assigned the opposite color, or already colored. In the latter case, success means the two colors were different, since equal colors would have caused a conflict. Therefore \(c(u)\ne c(v)\) for every edge. The color classes give a bipartition.

Now suppose a conflict is reported on an edge \(uv\), so \(c(u)=c(v)\). The vertices \(u\) and \(v\) belong to the same BFS tree: a color is assigned only within a search started at a single root, and colors are never transferred between searches. Along every parent edge in that tree the assigned colors are opposite. Consequently, any proper two-coloring of the graph would have to alternate colors along the unique tree path from the root to each vertex. Because the algorithm assigned \(u\) and \(v\) the same color, their tree paths have the same parity of length, so any proposed proper two-coloring would put \(u\) and \(v\) in the same part. But \(uv\) is an edge, and the endpoints of an edge must be in different parts. This contradiction shows that no proper two-coloring exists. Hence a conflict proves the graph is not bipartite. Together, these arguments prove both directions. \(\square\)

The conflict is meaningful because the algorithm’s earlier assignments are constrained by the BFS tree. A same-color edge contradicts those constraints; it is not a signal to recolor one endpoint and continue. The exact report is an edge whose endpoints cannot both satisfy the required opposite-color rule under the assignments forced by the search tree.

Worked Examples

Worked Example: A Successful Search on a Six-Cycle

Let \(G=(V,E)\), where \(V=\{1,2,3,4,5,6\}\) and \(E=\{12,13,24,35,46,56\}\). Start at vertex \(1\), and inspect neighbors in increasing numerical order. The table lists the queue after each operation. A color assignment is written as, for example, \(2:1\).

OperationQueue afterwardNew assignment or check
Initialize at 1\([1]\)\(1:0\)
Remove 1\([2,3]\)Assign \(2:1\), then \(3:1\)
Remove 2\([3,4]\)Check \(12\); assign \(4:0\) from \(24\)
Remove 3\([4,5]\)Check \(13\) and \(35\); assign \(5:0\)
Remove 4\([5,6]\)Check \(24\); assign \(6:1\) from \(46\)
Remove 5\([6]\)Check \(35\) and \(56\); \(c(5)=0\), \(c(6)=1\)
Remove 6\([]\)Check \(46\) and \(56\); both have opposite colors

The resulting color classes are \(\{1,4,5\}\) and \(\{2,3,6\}\). Checking the full edge set gives \(12\), \(13\), \(24\), \(35\), \(46\), and \(56\); in each pair the endpoint colors differ. Thus the algorithm succeeds and the displayed classes are a bipartition.

Check: the six edges are exactly the six consecutive pairs in the cycle \(1,2,4,6,5,3,1\). The classes alternate around that cycle, including across the closing edge \(31\), so the coloring also checks directly against the cycle description.

Worked Example: A Conflict on a Triangle

Let \(G=(V,E)\), where \(V=\{1,2,3\}\) and \(E=\{12,13,23\}\). Start at \(1\), with neighbors inspected in increasing order.

OperationQueue afterwardNew assignment or check
Initialize at 1\([1]\)\(1:0\)
Remove 1\([2,3]\)Assign \(2:1\), then \(3:1\)
Remove 2\([3]\)Check \(12\); on \(23\), find \(c(3)=c(2)=1\)

The algorithm reports a conflict on \(23\) and stops. Vertex \(3\) remains colored \(1\); the procedure does not replace that color. The theorem shows that the conflict rules out a proper two-coloring of this graph.

Check: the three edges are \(12\), \(13\), and \(23\). If \(1\) has color \(0\), then edges \(12\) and \(13\) require both \(2\) and \(3\) to have color \(1\). Edge \(23\) then has equal-colored endpoints, confirming the reported conflict.

Worked Example: Disconnected Graph with an Isolated Vertex

Let \(G=(V,E)\), where \(V=\{1,2,3,4,5,6,7\}\) and \(E=\{12,23,34,56\}\). Scan vertices in increasing order, and inspect neighbors in increasing order. The components are searched separately.

OperationQueue afterwardNew assignment or check
Start at 1\([1]\)\(1:0\)
Remove 1\([2]\)Assign \(2:1\)
Remove 2\([3]\)Check \(12\); assign \(3:0\)
Remove 3\([4]\)Check \(23\); assign \(4:1\)
Remove 4\([]\)Check \(34\)
Restart at 5; remove 5\([6]\)Assign \(5:0\), then \(6:1\)
Remove 6\([]\)Check \(56\)
Restart at 7; remove 7\([]\)Assign \(7:0\); it has no neighbors

The color classes are \(\{1,3,5,7\}\) and \(\{2,4,6\}\). Checking \(12\), \(23\), \(34\), and \(56\), every edge has endpoints in different classes. Therefore the graph is bipartite. The isolated vertex \(7\) is included in a class even though it contributes no edge.

Check: the first component has three edges, the second has one, and the isolated component has zero, accounting for all four edges in \(E\). All four listed edges cross the displayed classes.

Common Mistake: Overwriting a Color

Consider the triangle in the second example. After processing vertex \(1\), both \(2\) and \(3\) have color \(1\). When processing \(2\), the edge \(23\) is a conflict because \(c(2)=c(3)=1\).

A faulty version of the algorithm might first set \(c(3)=1-c(2)=0\), then compare \(c(3)\) with \(c(2)\). It would compare \(0\) with \(1\) and miss the conflict. That is a wrong calculation: it has changed the value it needed to test. The correct order is to test whether \(3\) is uncolored. Since it is already colored, leave \(c(3)=1\) unchanged, compare it with \(c(2)=1\), and report the conflict.

Takeaway: Assign the opposite color only to an uncolored neighbor. For a colored neighbor, preserve its color and test whether it agrees with the current vertex. A same-color edge is evidence of failure, not a reason to overwrite a label.

Running Time and Use

With adjacency lists, each vertex is enqueued and removed from the queue at most once. Each undirected edge appears in the adjacency list of each endpoint, so its two incidences are inspected at most once each. The outer scan examines all \(n=|V|\) vertices. Thus the total running time is \(O(n+m)\), where \(m=|E|\); the color array, parent array, and queue use \(O(n)\) additional space. The algorithm gives both an answer and, on success, an explicit two-part assignment that can be checked edge by edge.

The result also explains why disconnected inputs require care. A successful search from one root establishes a coloring only for that component. The outer scan must restart from each remaining uncolored vertex; otherwise an untested component could contain a conflict. Conversely, isolated vertices need no special rejection: they receive a color when chosen as roots, and since they have no incident edges, they cannot create a conflict.

Check Your Understanding

For each question, distinguish an assignment to a previously uncolored vertex from a check of an already-colored one.

  1. Run the algorithm on \(V=\{a,b,c,d\}\), \(E=\{ab,bc,cd\}\), starting at \(a\). Give the queue after each removal and the two color classes.
  2. In a graph with edges \(12,13,23\), why must the algorithm leave vertex \(3\)'s color unchanged when it inspects edge \(23\)?
  3. Prove that if a graph is bipartite, no component search can report a conflict, regardless of which root is chosen.
  4. Why is it not enough to run BFS from one vertex when testing a graph that may be disconnected?
  5. Using adjacency lists, explain why each undirected edge contributes at most two neighbor inspections.