DSatur Algorithm for Graph Coloring - GeeksforGeeks

08 Nov, 2021
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.
Proper three-colouring of a graph
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.
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(n2), 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(n2) 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 vertexstruct 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 graphclass 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 colorsvoid 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 Codeint 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;}
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) 
rhydianlewis
Binary Tree
Graph Coloring
Algorithms
Combinatorial
Graph
Graph
Combinatorial
Algorithms
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
DSA Sheet by Love Babbar
Difference between Informed and Uninformed Search in AI
SCAN (Elevator) Disk Scheduling Algorithms
Quadratic Probing in Hashing
K means Clustering - Introduction
Write a program to print all permutations of a given string
Permutation and Combination in Python
itertools.combinations() module in Python to print all possible combinations
Combinational Sum
Count ways to reach the nth stair using step 1, 2 or 3