Open In App

DSatur Algorithm for Graph Coloring

Last Updated : 04 Apr, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

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

  1. 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.
  2. 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.
  3. 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 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;
}


Python3




import heapq
 
 
class nodeInfo:
    def __init__(self, sat, deg, vertex):
        self.sat = sat
        self.deg = deg
        self.vertex = vertex
 
 
class maxSat:
    def __call__(self, node):
        return (-node.sat, -node.deg, node.vertex)
 
 
class Graph:
    def __init__(self, numNodes):
        self.n = numNodes
        self.adj = [[] for i in range(numNodes)]
 
    def addEdge(self, u, v):
        self.adj[u].append(v)
        self.adj[v].append(u)
 
    def DSatur(self):
        used = [False] * self.n
        c = [-1] * self.n
        d = [len(self.adj[u]) for u in range(self.n)]
        adjCols = [set() for u in range(self.n)]
        Q = []
 
        for u in range(self.n):
            heapq.heappush(Q, (maxSat()(nodeInfo(0, d[u], u)), u))
 
        while Q:
            maxPtr, u = heapq.heappop(Q)
            while Q and maxPtr == Q[0][0]:
                _, v = heapq.heappop(Q)
                heapq.heappush(
                    Q, (maxSat()(nodeInfo(len(adjCols[v]), d[v], v)), v))
 
            for v in self.adj[u]:
                if c[v] != -1:
                    used] = True
            for i in range(self.n):
                if not used[i]:
                    break
            for v in self.adj[u]:
                if c[v] != -1:
                    used] = False
            c[u] = i
            for v in self.adj[u]:
                if c[v] == -1:
                    heapq.heappush(
                        Q, (maxSat()(nodeInfo(len(adjCols[v]), d[v], v)), v))
                    adjCols[v].add(i)
                    d[v] -= 1
 
        for u in range(self.n):
            print(f"Vertex {u} --->  Color {c[u]}")
 
 
# Driver Code
if __name__ == '__main__':
    G1 = Graph(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)
    print("Coloring of graph G1 ")
    G1.DSatur()
 
    G2 = Graph(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)
    print("\nColoring of graph G2 ")
    G2.DSatur()
 
    # This code is conytributed by Akash Jha


Java




import java.util.*;
 
class NodeInfo {
    int sat;
    int deg;
    int vertex;
 
    NodeInfo(int sat, int deg, int vertex)
    {
        this.sat = sat;
        this.deg = deg;
        this.vertex = vertex;
    }
}
 
class MaxSat implements Comparator<NodeInfo> {
    @Override public int compare(NodeInfo lhs, NodeInfo rhs)
    {
        if (lhs.sat != rhs.sat) {
            return Integer.compare(rhs.sat, lhs.sat);
        }
        else if (lhs.deg != rhs.deg) {
            return Integer.compare(rhs.deg, lhs.deg);
        }
        else {
            return Integer.compare(rhs.vertex, lhs.vertex);
        }
    }
}
 
class Graph {
    private int n;
    private ArrayList<ArrayList<Integer> > adj;
 
    Graph(int numNodes)
    {
        n = numNodes;
        adj = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            adj.add(new ArrayList<>());
        }
    }
 
    void addEdge(int u, int v)
    {
        adj.get(u).add(v);
        adj.get(v).add(u);
    }
 
    void DSatur()
    {
        int u, i;
        boolean[] used = new boolean[n];
        int[] c = new int[n];
        int[] d = new int[n];
        HashSet<Integer>[] adjCols = new HashSet[n];
        PriorityQueue<NodeInfo> Q
            = new PriorityQueue<>(new MaxSat());
 
        for (u = 0; u < n; u++) {
            c[u] = -1;
            d[u] = adj.get(u).size();
            adjCols[u] = new HashSet<>();
            Q.add(new NodeInfo(0, d[u], u));
        }
 
        while (!Q.isEmpty()) {
            NodeInfo maxPtr = Q.poll();
            u = maxPtr.vertex;
            for (int v : adj.get(u)) {
                if (c[v] != -1) {
                    used] = true;
                }
            }
            for (i = 0; i < used.length; i++) {
                if (!used[i]) {
                    break;
                }
            }
            for (int v : adj.get(u)) {
                if (c[v] != -1) {
                    used] = false;
                }
            }
            c[u] = i;
            for (int v : adj.get(u)) {
                if (c[v] == -1) {
                    Q.remove(new NodeInfo(adjCols[v].size(),
                                          d[v], v));
                    adjCols[v].add(i);
                    d[v]--;
                    Q.add(new NodeInfo(adjCols[v].size(),
                                       d[v], v));
                }
            }
        }
 
        for (u = 0; u < n; u++) {
            System.out.println("Vertex " + u
                               + " --->  Color " + c[u]);
        }
    }
}
 
public class Main {
    public static void main(String[] args)
    {
        Graph G1 = new Graph(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);
        System.out.println("Coloring of graph G1:");
        G1.DSatur();
 
        Graph G2 = new Graph(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);
        System.out.println("\nColoring of graph G2: ");
        G2.DSatur();
    }
}
// This code is conytributed by Akash Jha


C#




using System;
using System.Collections.Generic;
 
namespace GraphColoring {
struct nodeInfo
{
    public int sat;
    public int deg;
    public int vertex;
}
class Graph {
    int n;
    List<List<int> > adj;
 
    public Graph(int numNodes)
    {
        n = numNodes;
        adj = new List<List<int> >();
        for (int i = 0; i < n; i++)
            adj.Add(new List<int>());
    }
 
    public void addEdge(int u, int v)
    {
        adj[u].Add(v);
        adj[v].Add(u);
    }
 
    public void DSatur()
    {
        int u, i;
        List<bool> used = new List<bool>(new bool[n]);
        List<int> c = new List<int>(new int[n]);
        List<int> d = new List<int>(new int[n]);
        List<HashSet<int> > adjCols
            = new List<HashSet<int> >();
        SortedSet<nodeInfo> Q
            = new SortedSet<nodeInfo>(new maxSat());
 
        for (u = 0; u < n; u++) {
            c[u] = -1;
            d[u] = adj[u].Count;
            adjCols.Add(new HashSet<int>());
            Q.Add(new nodeInfo{ sat = 0, deg = d[u],
                                vertex = u });
        }
 
        while (Q.Count > 0) {
            nodeInfo maxNode = Q.Min;
            u = maxNode.vertex;
            Q.Remove(maxNode);
            foreach(int v in adj[u])
            {
                if (c[v] != -1)
                    used] = true;
            }
            for (i = 0; i < used.Count; i++) {
                if (used[i] == false)
                    break;
            }
            foreach(int v in adj[u])
            {
                if (c[v] != -1)
                    used] = false;
            }
            c[u] = i;
            foreach(int v in adj[u])
            {
                if (c[v] == -1) {
                    Q.Remove(new nodeInfo{
                        sat = adjCols[v].Count, deg = d[v],
                        vertex = v });
                    adjCols[v].Add(i);
                    d[v]--;
                    Q.Add(new nodeInfo{
                        sat = adjCols[v].Count, deg = d[v],
                        vertex = v });
                }
            }
        }
 
        for (u = 0; u < n; u++) {
            Console.WriteLine(
                $ "Vertex {u} ---> Color {c[u]}");
        }
    }
 
    class maxSat : IComparer<nodeInfo> {
        public int Compare(nodeInfo lhs, nodeInfo rhs)
        {
            if (lhs.sat != rhs.sat)
                return rhs.sat.CompareTo(lhs.sat);
            else if (lhs.deg != rhs.deg)
                return rhs.deg.CompareTo(lhs.deg);
            else
                return rhs.vertex.CompareTo(lhs.vertex);
        }
    }
}
 
class Program {
    static void Main(string[] args)
    {
        Graph G1 = new Graph(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);
        Console.WriteLine("Coloring of graph G1");
        G1.DSatur();
 
        Graph G2 = new Graph(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);
        Console.WriteLine("Coloring of graph G2");
 
        G2.DSatur();
    }
}
 
// This code is conytributed by Akash Jha


Javascript




class Graph {
  constructor(numNodes) {
    this.n = numNodes;
    this.adj = new Array(numNodes).fill().map(() => []);
  }
 
  addEdge(u, v) {
    this.adj[u].push(v);
    this.adj[v].push(u);
  }
 
  DSatur() {
    let u, i;
    const used = new Array(this.n).fill(false);
    const c = new Array(this.n).fill(-1);
    const d = new Array(this.n);
    const adjCols = new Array(this.n).fill().map(() => new Set());
    const maxSat = (lhs, rhs) => (
      lhs.sat !== rhs.sat ? lhs.sat > rhs.sat :
      lhs.deg !== rhs.deg ? lhs.deg > rhs.deg :
      lhs.vertex > rhs.vertex
    );
    const Q = new Set();
 
    for (u = 0; u < this.n; u++) {
      c[u] = -1;
      d[u] = this.adj[u].length;
      adjCols[u] = new Set();
      Q.add({ sat: 0, deg: d[u], vertex: u });
    }
 
    while (Q.size > 0) {
      const maxPtr = Q.values().next().value;
      u = maxPtr.vertex;
      Q.delete(maxPtr);
      for (const v of this.adj[u]) {
        if (c[v] !== -1) {
          used] = true;
        }
      }
      for (i = 0; i < used.length; i++) {
        if (!used[i]) {
          break;
        }
      }
      for (const v of this.adj[u]) {
        if (c[v] !== -1) {
          used] = false;
        }
      }
      c[u] = i;
      for (const v of this.adj[u]) {
        if (c[v] === -1) {
          const vSet = adjCols[v];
          Q.delete({ sat: vSet.size, deg: d[v], vertex: v });
          vSet.add(i);
          d[v]--;
          Q.add({ sat: vSet.size, deg: d[v], vertex: v });
        }
      }
    }
 
    for (u = 0; u < this.n; u++) {
      console.log(`Vertex ${u} --->  Color ${c[u]}`);
    }
  }
}
 
const G1 = new Graph(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);
console.log("Coloring of graph G1");
G1.DSatur();
 
const G2 = new Graph(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);
console.log("\nColoring of graph G2");
G2.DSatur();
 
//This code is conytributed by Akash Jha


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) 



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads