# DSatur Algorithm for Graph Coloring

Graph colouring is the task of assigning colours to the vertices of a graph so that:

- pairs of adjacent vertices are assigned different colours, and
- the number of different colours used across the graph is minimal.

The following graph has been coloured using just three colours (red, blue and green here). This is actually the minimum number of colours needed for this particular graph – that is, we cannot colour this graph using fewer than three colours while ensuring that adjacent vertices are coloured differently.

The minimum number of colours needed to colour a graph **G** is known as the **chromatic number** and is usually denoted by **χ(G)**. Determining the chromatic number of a graph is NP-hard. The corresponding decision problem of deciding whether a **k**-colouring exists for a graph G is also NP-complete.

Similar posts on this website have already described the greedy algorithm for graph colouring. This algorithm is simple to apply, but the number of colours used in its solutions depends very much on the order that the vertices are considered. In the best case, the right ordering will produce a solution using **χ(G)** colours; however bad orderings can result in solutions using many additional colours.

The** DSatur algorithm** (abbreviated from “**degree of saturation**”) has similar behaviour to the Greedy algorithm. The difference lies in the way that it generates the vertex ordering. Specifically, the next vertex to colour is always chosen as the uncoloured vertex with the **highest saturation degree**. The saturation degree of a vertex is defined as the number of different colours currently assigned to neighbouring vertices. Other rules are also then used to break ties.

Let **G** be a graph with **n** vertices and **m** edges. In addition, assume that we will use the colour labels **0, 1, 2, …, n-1**. (More than **n** colours are never required in a solution). The **DSatur algorithm** operates as follows

- Let
**v**be the uncoloured vertex in**G**with the largest saturation degree. In cases of ties, choose the vertex among these with the largest degree in the subgraph induced by the uncoloured vertices. Further ties can be broken arbitrarily. - Assign
**v**to colour**i**, where**i**is the smallest integer from the set**{0, 1, 2, …, n}**that is not currently assigned to any neighbour of**v**. - If there remain uncoloured vertices, repeat all steps again, otherwise, end at this step.

The DSatur algorithm is similar to the Greedy algorithm in that once a vertex has been selected, it is assigned to the lowest colour label not assigned to any of its neighbours. The actions of Step 1, therefore, provide the main power behind the algorithm in that they prioritise vertices that are seen to be the “**most constrained**” – that is, vertices that currently have the fewest colour options available to them. Consequently, these “more constrained” vertices are dealt with first, allowing the less constrained vertices to be coloured later.

**Analysis of DSatur**

Because the DSatur algorithm generates a vertex ordering during execution, the number of colours it uses is more predictable than the greedy algorithm. Its solutions also tend to have fewer colours than those of the greedy algorithm. One feature of the algorithm is that, if a graph is composed of multiple components, then all vertices of a single component will be coloured before the other vertices are considered. DSatur is also exact for several graph topologies including bipartite graphs, cycle graphs and wheel graphs. (With these graphs, a solution using **χ(G)** colours will always be produced.)

The overall complexity of the DSatur algorithm is **O(n ^{2})**, where

**n**is the number of vertices in the graph. This can be achieved by performing

**n**separate applications of an

**O(n)**process that:

- Identifies the next vertex to colour according to DSatur’s selection rules.
- Colours this vertex.

Below we present a C++ implementation of DSatur that operates in **O((n + m) log n)** time, where **m** is the number of edges in the graph. This is much faster than **O(n ^{2})** for all but the densest of graphs. This implementation involves using a red-black binary tree to store all vertices that are not yet coloured, together with their saturation degrees and their degrees in the subgraph induced by the uncoloured vertices. Red-black trees are a type of self-balancing binary tree that are used with the

**set**container in C++’s standard template library. This allows the selection of the next vertex to colour (according to DSatur’s selection rules) to be performed in constant time. It also allows items to be inserted and removed in logarithmic time.

## C++

`// A C++ program to implement the DSatur algorithm for graph` `// coloring` `#include <iostream>` `#include <set>` `#include <tuple>` `#include <vector>` `using` `namespace` `std;` `// Struct to store information` `// on each uncoloured vertex` `struct` `nodeInfo {` ` ` `int` `sat; ` `// Saturation degree of the vertex` ` ` `int` `deg; ` `// Degree in the uncoloured subgraph` ` ` `int` `vertex; ` `// Index of vertex` `};` `struct` `maxSat {` ` ` `bool` `operator()(` `const` `nodeInfo& lhs,` ` ` `const` `nodeInfo& rhs) ` `const` ` ` `{` ` ` `// Compares two nodes by` ` ` `// saturation degree, then` ` ` `// degree in the subgraph,` ` ` `// then vertex label` ` ` `return` `tie(lhs.sat, lhs.deg, lhs.vertex)` ` ` `> tie(rhs.sat, rhs.deg, rhs.vertex);` ` ` `}` `};` `// Class representing` `// an undirected graph` `class` `Graph {` ` ` `// Number of vertices` ` ` `int` `n;` ` ` `// Number of vertices` ` ` `vector<vector<` `int` `> > adj;` `public` `:` ` ` `// Constructor and destructor` ` ` `Graph(` `int` `numNodes)` ` ` `{` ` ` `n = numNodes;` ` ` `adj.resize(n, vector<` `int` `>());` ` ` `}` ` ` `~Graph() { adj.clear(); }` ` ` `// Function to add an edge to graph` ` ` `void` `addEdge(` `int` `u, ` `int` `v);` ` ` `// Colour the graph` ` ` `// using the DSatur algorithm` ` ` `void` `DSatur();` `};` `void` `Graph::addEdge(` `int` `u, ` `int` `v)` `{` ` ` `adj[u].push_back(v);` ` ` `adj[v].push_back(u);` `}` `// Assigns colors (starting from 0)` `// to all vertices and` `// prints the assignment of colors` `void` `Graph::DSatur()` `{` ` ` `int` `u, i;` ` ` `vector<` `bool` `> used(n, ` `false` `);` ` ` `vector<` `int` `> c(n), d(n);` ` ` `vector<set<` `int` `> > adjCols(n);` ` ` `set<nodeInfo, maxSat> Q;` ` ` `set<nodeInfo, maxSat>::iterator maxPtr;` ` ` `// Initialise the data structures.` ` ` `// These are a (binary` ` ` `// tree) priority queue, a set` ` ` `// of colours adjacent to` ` ` `// each uncoloured vertex` ` ` `// (initially empty) and the` ` ` `// degree d(v) of each uncoloured` ` ` `// vertex in the graph` ` ` `// induced by uncoloured vertices` ` ` `for` `(u = 0; u < n; u++) {` ` ` `c[u] = -1;` ` ` `d[u] = adj[u].size();` ` ` `adjCols[u] = set<` `int` `>();` ` ` `Q.emplace(nodeInfo{ 0, d[u], u });` ` ` `}` ` ` `while` `(!Q.empty()) {` ` ` `// Choose the vertex u` ` ` `// with highest saturation` ` ` `// degree, breaking ties with d.` ` ` `// Remove u from the priority queue` ` ` `maxPtr = Q.begin();` ` ` `u = (*maxPtr).vertex;` ` ` `Q.erase(maxPtr);` ` ` `// Identify the lowest feasible` ` ` `// colour i for vertex u` ` ` `for` `(` `int` `v : adj[u])` ` ` `if` `(c[v] != -1)` ` ` `used] = ` `true` `;` ` ` `for` `(i = 0; i < used.size(); i++)` ` ` `if` `(used[i] == ` `false` `)` ` ` `break` `;` ` ` `for` `(` `int` `v : adj[u])` ` ` `if` `(c[v] != -1)` ` ` `used] = ` `false` `;` ` ` `// Assign vertex u to colour i` ` ` `c[u] = i;` ` ` `// Update the saturation degrees and` ` ` `// degrees of all uncoloured neighbours;` ` ` `// hence modify their corresponding` ` ` `// elements in the priority queue` ` ` `for` `(` `int` `v : adj[u]) {` ` ` `if` `(c[v] == -1) {` ` ` `Q.erase(` ` ` `{ ` `int` `(adjCols[v].size()),` ` ` `d[v], v });` ` ` `adjCols[v].insert(i);` ` ` `d[v]--;` ` ` `Q.emplace(nodeInfo{` ` ` `int` `(adjCols[v].size()),` ` ` `d[v], v });` ` ` `}` ` ` `}` ` ` `}` ` ` `// The full graph has been coloured.` ` ` `// Print the result` ` ` `for` `(u = 0; u < n; u++)` ` ` `cout << ` `"Vertex "` `<< u` ` ` `<< ` `" ---> Color "` `<< c[u]` ` ` `<< endl;` `}` `// Driver Code` `int` `main()` `{` ` ` `Graph G1(5);` ` ` `G1.addEdge(0, 1);` ` ` `G1.addEdge(0, 2);` ` ` `G1.addEdge(1, 2);` ` ` `G1.addEdge(1, 3);` ` ` `G1.addEdge(2, 3);` ` ` `G1.addEdge(3, 4);` ` ` `cout << ` `"Coloring of graph G1 \n"` `;` ` ` `G1.DSatur();` ` ` `Graph G2(5);` ` ` `G2.addEdge(0, 1);` ` ` `G2.addEdge(0, 2);` ` ` `G2.addEdge(1, 2);` ` ` `G2.addEdge(1, 4);` ` ` `G2.addEdge(2, 4);` ` ` `G2.addEdge(4, 3);` ` ` `cout << ` `"\nColoring of graph G2 \n"` `;` ` ` `G2.DSatur();` ` ` `return` `0;` `}` |

**Output**

Coloring of graph G1 Vertex 0 ---> Color 0 Vertex 1 ---> Color 2 Vertex 2 ---> Color 1 Vertex 3 ---> Color 0 Vertex 4 ---> Color 1 Coloring of graph G2 Vertex 0 ---> Color 0 Vertex 1 ---> Color 2 Vertex 2 ---> Color 1 Vertex 3 ---> Color 1 Vertex 4 ---> Color 0

The first part of this implementation involves initialising the data structures. This involves looping through each of the vertices and populating the red-black tree. This takes** O(n log n)** time. In the main part of the algorithm, the red-black tree allows the selection of the next vertex **u** to colour to be performed in constant time. Once** u **has been coloured, items corresponding to the uncoloured neighbours of **u** need to be updated in the red-black tree. Doing this for every vertex results in a total run time of **O(m log n)**. Consequently, the overall run time is **O((n log n) + (m log n)) **= **O((n+m) log n)**.

Further information on this algorithm and others for graph colouring can be found in the book: *A Guide to Graph Colouring: Algorithms and Applications* (2021)