A Short History of Graph Coloring
Coloring began as a structural question—how many labels are needed so adjacent regions or vertices differ—and later became a central language for algorithmic complexity and constraint solving.
The resulting contrast is unusually sharp: 2-colorability has a linear-time structural test, while every fixed $k \geq 3$ gives an NP-complete decision problem on general graphs.
Coloring as a Constraint Problem
A proper $k$-coloring is a function $c:V\to\{0,\ldots,k-1\}$ satisfying $c(u)\ne c(v)$ for every edge $(u,v)$. Each color class is therefore an independent set. The smallest feasible $k$ is the chromatic number $\chi(G)$.
Decision
“Is $G$ colorable with at most $k$ colors?” The algorithms in this article answer this yes/no question and return a witness coloring when one exists.
Optimization
“What is $\chi(G)$?” Solve several decision problems, using clique lower bounds and heuristic upper bounds to narrow the candidate values of $k$.
2-Colorability: BFS/DFS Bipartiteness Test
Two colors are special because an edge leaves no choice: once $c(u)$ is known, every neighbor $v$ must receive $1-c(u)$. A traversal can therefore propagate forced colors instead of guessing them.
Seed one component
Choose any uncolored vertex, assign color 0, and store parent and depth information. Repeat this step because the graph may be disconnected.
Propagate parity
For every explored edge $(u,v)$, give an uncolored $v$ the opposite color. In BFS, color is simply distance parity from the component seed.
Detect a contradiction
If an edge joins two vertices with the same color, stop: the edge and the two parent paths expose an odd cycle.
Return a certificate
If every component finishes without conflict, the two color classes are a constructive certificate that the graph is bipartite.
Why This Works
Every walk from the seed alternates colors. If two routes force different colors onto one vertex, their union contains an odd cycle. Conversely, without an odd cycle, every route to a vertex has the same parity, so the assignment is consistent.
Turning “No” into an Odd-Cycle Certificate
A boolean answer is useful; a witness is better. Keep a parent pointer for every discovered vertex. When a conflict edge $(u,v)$ joins equal colors, walk both parent chains to their lowest common ancestor $a$. Concatenate the path $u\leadsto a$, the reversed path $a\leadsto v$, and the conflict edge $(v,u)$.
Because $u$ and $v$ have the same parity, their tree path has even length. Closing it with one more edge makes the cycle odd. The Python implementation below returns this closed vertex sequence so callers can inspect, test, or display the exact obstruction.
k-Colorability: Backtracking Search
For $k\geq3$, an edge no longer determines one forced choice: after seeing a red neighbor, a vertex may still choose blue, green, or another available color. The problem becomes a constraint-satisfaction search:
Variables and domains
Each vertex is a variable whose domain is ${0,\ldots,k-1}$. Assigning a color removes that color from every uncolored neighbor's domain.
Binary constraints
Every edge $(u,v)$ contributes the constraint $c(u)\ne c(v)$. A partial coloring is safe only while all assigned endpoints respect it.
Choose an uncolored vertex
A fixed order is correct, but a most-constrained-first order usually exposes contradictions earlier.
Try an available color
Skip colors already used by colored neighbors, assign a remaining color, and propagate the reduced domains.
Recurse or undo
If every vertex has a color, return the witness. If some uncolored vertex has no available color, undo the last choice and try another branch.
Search Heuristics & Propagation
Heuristics do not change which instances are colorable; they change how quickly the search discovers a witness or contradiction. The exact Python solver combines three inexpensive ideas:
- DSATUR ordering: choose the uncolored vertex adjacent to the largest number of distinct colors. Break ties by degree, then vertex index for deterministic output.
- Forward checking: after each assignment, reject immediately if any uncolored vertex has lost every color.
- Color symmetry breaking: when a search component has no colored neighbor, try color 0 first. Renaming colors produces equivalent solutions, so symmetric root branches add no information.
Worked Example
Even cycle: propagation succeeds
For the square 0-1-2-3-0, start at vertex 0 with teal. BFS forces vertices 1 and 3 to navy, then vertex 2 to teal. The closing edge $(2,3)$ joins teal to navy, so no conflict appears.
| BFS event | Assignment | Invariant |
|---|---|---|
| Seed 0 | $c(0)=0$ | depth 0 is even |
| Explore 0 | $c(1)=c(3)=1$ | both neighbors use the opposite color |
| Explore 1 | $c(2)=0$ | distance parity determines the color |
| Check remaining edges | no change | every edge crosses the partition |
$K_4$: three colors fail
Vertices 0, 1, and 2 form a triangle, so they must use three distinct colors. Vertex 3 is adjacent to all three; each candidate color conflicts with one neighbor. Every branch is equivalent up to renaming the three colors, so backtracking eventually proves that $K_4$ is not 3-colorable. A fourth color immediately supplies a witness.
Complexity Analysis
The two algorithms differ because propagation is complete for two colors but incomplete for three or more. Let $n=|V|$ and $m=|E|$.
| Problem | Worst-case time | Space | What is returned? |
|---|---|---|---|
| Bipartite test | $O(n+m)$ | $O(n)$ besides the graph | two color classes or an odd cycle |
| Exact $k$-color search | $O(k^n(n+m))$ with straightforward checks; at most $k^n$ complete assignments | $O(n+k)$ search state | a $k$-coloring or failure after exhaustive search |
The boundary cases are worth handling explicitly: $k=1$ succeeds only for an edgeless graph; $k=2$ should use the linear bipartite test; and any loop-free graph with $k\geq n$ is trivially colorable. For every fixed $k\geq3$, the decision problem is NP-complete on general graphs.
Implementation
The Python version returns both kinds of bipartite certificate and uses DSATUR plus forward checking for exact $k$-color search. The C++ and Java tabs keep the same graph model and demonstrate compact decision versions. All snippets assume an undirected adjacency list; each undirected edge must appear in both endpoint lists.
Real-World Applications
The mathematical model is useful whenever pairwise incompatibilities must be separated. The graph must be defined carefully: vertices represent choices or resources, and an edge must mean “these two cannot share a group.”
Bipartite matching
Before running Hopcroft–Karp, a bipartite test verifies the required two-sided structure and supplies the left/right partition used by the matching algorithm.
Conflict scheduling
Model exams or jobs as vertices and overlaps as edges. A color is a time slot; minimizing colors corresponds to minimizing the number of slots under this simplified conflict model.
Register allocation
Compiler variables that are simultaneously live interfere. Coloring the interference graph maps nonadjacent variables to the same machine register.
SAT and CSP research
$k$-coloring is a compact source of hard constraint instances. It can be encoded with Boolean variables such as “vertex $v$ uses color $c$” and solved by a general SAT engine.
Operational value of certificates
A valid coloring can be checked by scanning every edge. When bipartiteness fails, an odd-cycle witness is equally easy to verify. These small certificates make failures explainable instead of leaving downstream code with an opaque false.
Exercises
1. Reconstruct the obstruction
Extend a DFS-based bipartite test to return an odd cycle. Why does a conflict edge between equal colors guarantee that the two tree depths have equal parity?
2. Test edge cases
Build cases containing an isolated vertex, multiple connected components, duplicate adjacency entries, and a self-loop. State the expected certificate for each before running the code.
3. Trace DSATUR
Run the vertex-selection rule by hand on $K_4$ with $k=3$. Record each vertex's saturation set after every assignment and identify the first empty domain.
4. Search for the chromatic number
Use a clique size as a lower bound and a greedy coloring as an upper bound. Repeatedly call the decision solver only for candidate values between those bounds.
5. SAT encoding challenge
Create a Boolean variable $x_{v,c}$ for each vertex-color pair. Add clauses requiring at least one color per vertex, at most one color per vertex, and different colors across each edge.
Limitations
The P vs. NP Cliff at k=3
There is no smooth complexity gradient here: 2-colorability is linear-time, but fixed-$k$ colorability is NP-complete for every $k\geq3$ on general graphs. DSATUR ordering and propagation may shrink the explored tree dramatically, but the worst case remains exponential.
- Decision is not optimization: a successful $k$-coloring proves $chi(G)\leq k$, not that $k$ is minimal.
- Model assumptions matter: these routines expect an undirected adjacency list. A self-loop makes every proper coloring impossible; parallel entries do not change colorability.
- Recursion can be deep: exact search may reach depth $n$ and can require an iterative stack or a larger recursion limit in production environments.
- Heuristics are instance-dependent: the best ordering can vary. Dense and sparse graphs may favor different tie-breakers, bounds, or decompositions.