M-coloring Problem

vaishnavi pandey
Last Updated: May 24, 2022
Difficulty Level :
MEDIUM

Introduction

To study graphs as mathematical structures, we use graph labeling. Graph labeling is a way of assigning labels to vertices, edges, or faces of a graph. It can be done in various ways. 

Graph coloring is one of the prime methods of labeling. It is a process of assigning labels or "colors" to elements of a graph following certain constraints.

In this blog, we'll discuss one such problem, known as the M-coloring problem. So, let's get started.

(Also see Data Structures

Problem Statement

The m-coloring problem states, "We are given an undirected graph and m number of different colors. We have to check if we can assign colors to the vertices of the graphs in such a way that no two adjacent vertices have the same color."

Let’s understand this problem with the help of an example.

Example: Given an adjacency matrix of an undirected graph and a number M having a value of 3. 

We've to check if we can color the above four vertices such that no two adjacent vertices have the same color. 

After assigning the colors, the graph will look like this.

 

In this graph, we can see that nodes 2 and 3 are not adjacent and have the same color; the remaining nodes have different colors. This is one possible solution.

Note: The m-coloring problem can have multiple solutions.

There are various ways to solve the M-coloring problem. We’ll be seeing some of them in this article. let’s move on with the brute force approach.

Recommended try the Problem yourself before moving on to the solution

Approach 1: Brute Force

This approach will try to find all the possible combinations in which vertices can be colored. After all the combinations are made, we'll check if it follows the constraints of graph coloring. If the constraints are satisfied, that will be the answer.

The implementation of the above approach is given below:

C++ Implementation

#include<bits/stdc++.h>

using namespace std;

//Number of vertices in the graph
#define V 4

//Function to display
void Display(int color[]) {
  cout << "Solution Exists \n The colors given to vertices are: "<<endl;
  for (int i = 0; i < V; i++)
    cout << "Vertex " << i + 1 << " is given color:" << color[i] << endl;
  cout << endl;
}

//Function to check constraints
bool satisfyConstraints(bool Adj_matrix[V][V], int color[]) {
  for (int i = 0; i < V; i++) {
    for (int j = i + 1; j < V; j++) {
      if (Adj_matrix[i][j] && color[j] == color[i]) {
        return false;
      }
    }
  }
  return true;
}
//Function for m-coloring problem
bool m_Coloring(bool Adj_matrix[V][V], int m, int i, int color[V]) {
  // if current index reached end
  if (i == V) {
    // if constraint is satisfied
    if (satisfyConstraints(Adj_matrix, color)) {
      Display(color);
      return true;
    }
    return false;
  }

  // Assign each color from 1 to m
  for (int j = 1; j <= m; j++) {
    color[i] = j;
    // For the rest of the vertices
    if (m_Coloring(Adj_matrix, m, i + 1, color))
      return true;
    color[i] = 0;
  }

  return false;
}

// Driver code
int main() {
  // The adjacency matrix of the graph
  bool Adj_matrix[V][V] = {
    {0,1,1,1},
    {1,0,0,1},
    {1,0,0,1},
    {1,1,1,0},
  };
  // Number of colors available
  int m = 3;

  // Initialising all vertices with the color values as 0
  int color[V];
  for (int i = 0; i < V; i++) {
    color[i] = 0;
  }

  if (m_Coloring(Adj_matrix, m, 0, color) == 0)
    cout << "No such arrangement exists!!" << endl;

  return 0;
}

Java Implementation

public class Main {
  // Number of vertices in the graph
  static int V = 4;

  /* A utility function to print solution */
  static void display(int[] color) {
    System.out.println("Solution Exists:\n" +
      "The colors given to vertices are: ");
    for (int i = 0; i < V; i++) {
      System.out.println("Vertex " + (i + 1) + " is given color : " + color[i]);
    }
  }

  // check if the colored
  // graph is safe or not
  static boolean isSafe(boolean[][] graph, int[] color) {
    // check for every edge in a graph
    for (int i = 0; i < V; i++)
      for (int j = i + 1; j < V; j++)
        if (graph[i][j] && color[j] == color[i])
          // if matches return false
          return false;
    // if not return true as it is safe
    return true;
  }

  static boolean graphColoring(boolean[][] graph, int m,
    int i, int[] color) {
    // if current index reached end
    if (i == V) {

      // if coloring is safe
      if (isSafe(graph, color)) {

        // Print the solution
        display(color);
        return true;
      }
      return false;
    }

    // Assign each color from 1 to m
    for (int j = 1; j <= m; j++) {
      color[i] = j;

      // Recur of the rest vertices
      if (graphColoring(graph, m, i + 1, color))
        return true;
      color[i] = 0;
    }
    return false;
  }

  public static void main(String[] args) {
    boolean[][] graph = {
      {
        false,
        true,
        true,
        true
      },
      {
        true,
        false,
        true,
        false
      },
      {
        true,
        true,
        false,
        true
      },
      {
        true,
        false,
        true,
        false
      },
    };
    int m = 3; // Number of colors

    // Initialize all color values as 0.
    // This initialization is needed
    // correct functioning of isSafe()
    int[] color = new int[V];
    for (int i = 0; i < V; i++)
      color[i] = 0;
    if (!graphColoring(graph, m, 0, color))
      System.out.println("Solution does not exist");
  }
}

 

Python Implementation

# Number of vertices in the graph
# define 4 4

# check if the colored
# graph is safe or not
def isSafe(graph, color):

	# check for every edge in the graph
	for i in range(4):
		for j in range(i + 1, 4):
			if (graph[i][j] and color[j] == color[i]):
				return False
	return True

def graphColoring(graph, m, i, color):

	# if current index reached end
	if (i == 4):

		# if coloring is safe
		if (isSafe(graph, color)):

			# Print the solution
			display(color)
			return True
		return False

	# Assign each color from 1 to m
	for j in range(1, m + 1):
		color[i] = j

		# Recur of the rest vertices
		if (graphColoring(graph, m, i + 1, color)):
			return True
		color[i] = 0
	return False

# /* A utility function to print solution */
def display(color):
	print("Solution Exists:" " Following are the assigned colors ")
	for i in range(4):
		print("Vertex", i+1 ," is given color: ",color[i])

# Driver code
if __name__ == '__main__':
	graph = [
		[ 0, 1, 1, 1 ],
		[ 1, 0, 1, 0 ],
		[ 1, 1, 0, 1 ],
		[ 1, 0, 1, 0 ],
	]
	m = 3 # Number of colors

	# Initialize all color values as 0.
	# This initialization is needed
	# correct functioning of isSafe()
	color = [0 for i in range(4)]

	if (not graphColoring(graph, m, 0, color)):
		print ("Solution does not exist")

 

Output

The colors given to vertices are:
Vertex 1 is given color:1
Vertex 2 is given color:2
Vertex 3 is given color:2
Vertex 4 is given color:3

Complexity Analysis

Time Complexity: O(M^V). 

Reason: Choosing out of M given colors for V vertices will lead to an O(M^V) combination. So the time complexity is O(M^V).

Space Complexity: O(V). 

Reason: The m_Coloring function will require O(V) space for the recursive call stack.

Approach 2: Backtracking

Backtracking is a general algorithm for solving constraint satisfaction problems. It accomplishes this by constructing a solution incrementally, one component at a time, discarding any solutions that fail to satisfy the problem's criteria at any point in time.

In the above approach, we've seen that we are creating combinations after assigning colors to vertices and then checking the constraints. A different solution to this problem is to only assign colors to a particular vertex if it satisfies the constraints.

We'd assign a color to each vertex from 1 to m and see if it has a different color than its next vertex. If we obtain a configuration in which each node is colored from 1 to m, and adjacent vertices are of different colors, this will be the answer. 

Let’s see the implementation of this approach.

C++ Implementation

#include <bits/stdc++.h>

using namespace std;

// Number of vertices in the Adj_matrix
#define V 4

//Function to display
void Display(int color[]) {
  cout << "The colors given to vertices are:" << endl;
  for (int i = 0; i < V; i++)
    cout << "Vertex " << i + 1 << " is given color:" << color[i] << endl;
  cout << endl;
}

//Function to check constraints
bool satisfyConstraints(int v, bool Adj_matrix[V][V], int color[], int c) {
  for (int i = 0; i < V; i++) {
    if (Adj_matrix[v][i] && c == color[i])
      return false;
  }
  return true;
}

/* A recursive utility function
to solve m-coloring problem */
bool m_Coloring_Helper(bool Adj_matrix[V][V], int m, int color[], int v) {

  //If all vertices are assigned a color
  if (v == V)
    return true;

  //Try different colors to vertex v
  for (int c = 1; c <= m; c++) {

    // Check if given color is valid 
    if (satisfyConstraints(v, Adj_matrix, color, c)) {
      color[v] = c;

      //Assign colors to rest of the vertices
      if (m_Coloring_Helper(Adj_matrix, m, color, v + 1) == true)
        return true;

      //Backtrack
      color[v] = 0;
    }
  }

  // If no color can be assigned
  return false;
}

bool m_Coloring(bool Adj_matrix[V][V], int m) {

  // Initialize all color values as 0.
  int color[V];
  for (int i = 0; i < V; i++) {
    color[i] = 0;
  }

  // Call m_Coloring_Helper() for vertex 0
  if (m_Coloring_Helper(Adj_matrix, m, color, 0) == false) {
    cout << "No such arrangement exists!!";
    return false;
  }

  // Print the solution
  Display(color);
  return true;
}

int main() {
  // The adjacency matrix of the graph
  bool Adj_matrix[V][V] = {
    {0,1,1,1},
    {1,0,0,1},
    {1,0,0,1},
    {1,1,1,0},
  };

  // Number of colors
  int m = 3;
  m_Coloring(Adj_matrix, m);
  return 0;
}

Java Implementation

class Main {
  final int V = 4;
  int color[];

  /* A utility function to check
  if the current color assignment
  is safe for vertex v */
  boolean isSafe(
    int v, int graph[][], int color[],
    int c) {
    for (int i = 0; i < V; i++)
      if (
        graph[v][i] == 1 && c == color[i])
        return false;
    return true;
  }

  /* A recursive utility function
  to solve m coloring problem */
  boolean graphColoringUtil(
    int graph[][], int m,
    int color[], int v) {
    /* base case: If all vertices are
    assigned a color then return true */
    if (v == V)
      return true;

    /* Consider this vertex v and try
    different colors */
    for (int c = 1; c <= m; c++) {
      /* Check if assignment of color c to v
      is fine*/
      if (isSafe(v, graph, color, c)) {
        color[v] = c;

        /* recur to assign colors to rest
        of the vertices */
        if (
          graphColoringUtil(
            graph, m,
            color, v + 1))
          return true;

        /* If assigning color c doesn't lead
        to a solution then remove it */
        color[v] = 0;
      }
    }

    /* If no color can be assigned to
    this vertex then return false */
    return false;
  }

  boolean graphColoring(int graph[][], int m) {
    // Initialize all color values as 0. This
    // initialization is needed correct
    // functioning of isSafe()
    color = new int[V];
    for (int i = 0; i < V; i++)
      color[i] = 0;

    // Call graphColoringUtil() for vertex 0
    if (
      !graphColoringUtil(
        graph, m, color, 0)) {
      System.out.println(
        "Solution does not exist");
      return false;
    }

    // display the solution
    display(color);
    return true;
  }

  /* A utility function to print solution */
  void display(int color[]) {
    System.out.println(
      "Solution Exists: Following" +
      " are the assigned colors");
    for (int i = 0; i < V; i++)
      System.out.println(" Vertex " + (i + 1) + " is given color : " + color[i] + " ");
    System.out.println();
  }

  // driver program to test above function
  public static void main(String args[]) {
    Main Coloring = new Main();
    int graph[][] = {{0, 1, 1, 1},{ 1, 0, 1, 0},{1, 1, 0, 1},{1, 0, 1, 0}};
    int m = 3; // Number of colors
    Coloring.graphColoring(graph, m);
  }
}

Python Implementation

# Python program for solution of M Coloring
# problem using backtracking

class Graph():

	def __init__(self, vertices):
		self.V = vertices
		self.graph = [[0 for column in range(vertices)]\
							for row in range(vertices)]

	# A utility function to check
	# if the current color assignment
	# is safe for vertex v
	def isSafe(self, v, colour, c):
		for i in range(self.V):
			if self.graph[v][i] == 1 and colour[i] == c:
			    return False
		return True
	
	# A recursive utility function to solve m
	# coloring problem
	def graphColourUtil(self, m, colour, v):
		if v == self.V:
			return True

		for c in range(1, m + 1):
			if self.isSafe(v, colour, c) == True:
				colour[v] = c
				if self.graphColourUtil(m, colour, v + 1) == True:
				    return True
				colour[v] = 0

	def graphColouring(self, m):
		colour = [0] * self.V
		if self.graphColourUtil(m, colour, 0) == None:
		    return False
		print ("Solution exist and Following are the assigned colours:")
		for i in range(4):
		    print("Vertex", i+1, "is given color: ",colour[i])
		return True

# Driver Code
g = Graph(4)
g.graph = [[0, 1, 1, 1], [1, 0, 1, 0], [1, 1, 0, 1], [1, 0, 1, 0]]
m = 3
g.graphColouring(m)

 

Output

The colors given to vertices are:
Vertex 1 is given color:1
Vertex 2 is given color:2
Vertex 3 is given color:2
Vertex 4 is given color:3

Complexity Analysis

Time Complexity: O(M^V). 

Reason: Choosing out of M given colors for V vertices will lead to an O(M^V) combination. So the time complexity is O(M^V).

Space Complexity: O(V). 

Reason: The M_Coloring function will require O(V) space for the recursive call stack.

The above two approaches have the same time complexity regardless of different algorithms. In our next approach, we’ll be discussing an optimal approach to solve the M-coloring problem.

Approach 3: Breadth-First Search

In this approach, first of all, we’ll color all the vertices of the graph with the first color. Then we’ll traverse the graph in a breadth-first fashion starting from the first unvisited node.

After reaching a node, we’ll check the color of all the adjacent nodes of the current node. If the current and the adjacent node have the same color, we’ll assign the next color to the adjacent node. Mark the current node as visited and move forward.

Let’s see the algorithm of this approach.

Step 1: Start the BFS traversal.

Step 2: Find out the neighboring (adjacent) nodes of the current node.

  • If the color of the current node and the adjacent node is the same, assign the next color to the adjacent node.
  • Check if the current node has been visited or not. Mark it as visited and add it to a queue if it hasn't been visited yet.

Step 3: Check the condition for color availability. If the condition becomes false, return.

Step 4: Do it for all the given nodes.

C++ Implementation

#include <bits/stdc++.h>

using namespace std;

// Number of vertices in the Adj_matrix
#define V 4

//Function to display
void Display(int color[]) {
  cout << "The colors given to vertices are:" << endl;
  for (int i = 0; i < V; i++)
    cout << "Vertex " << i + 1 << " is given color:" << color[i] << endl;
  cout << endl;
}

//Function to check constraints
bool satisfyConstraints(int v, bool Adj_matrix[V][V], int color[], int c) {
  for (int i = 0; i < V; i++) {
    if (Adj_matrix[v][i] && c == color[i])
      return false;
  }
  return true;
}

/* A recursive utility function
to solve m-coloring problem */
bool m_Coloring_Helper(bool Adj_matrix[V][V], int m, int color[], int v) {

  //If all vertices are assigned a color
  if (v == V)
    return true;

  //Try different colors to vertex v
  for (int c = 1; c <= m; c++) {

    // Check if given color is valid 
    if (satisfyConstraints(v, Adj_matrix, color, c)) {
      color[v] = c;

      //Assign colors to rest of the vertices
      if (m_Coloring_Helper(Adj_matrix, m, color, v + 1) == true)
        return true;

      //Backtrack
      color[v] = 0;
    }
  }

  // If no color can be assigned
  return false;
}

bool m_Coloring(bool Adj_matrix[V][V], int m) {

  // Initialize all color values as 0.
  int color[V];
  for (int i = 0; i < V; i++) {
    color[i] = 0;
  }

  // Call m_Coloring_Helper() for vertex 0
  if (m_Coloring_Helper(Adj_matrix, m, color, 0) == false) {
    cout << "No such arrangement exists!!";
    return false;
  }

  // Print the solution
  Display(color);
  return true;
}

int main() {
  // The adjacency matrix of the graph
  bool Adj_matrix[V][V] = {
    {0,1,1,1},
    {1,0,0,1},
    {1,0,0,1},
    {1,1,1,0},
  };

  // Number of colors
  int m = 3;
  m_Coloring(Adj_matrix, m);
  return 0;
}

Java Implementation

// Java Implementation
import java.io.*;
import java.util.*;
class Node {

  // A node class which stores the color and the edges connected to the node
  int color = 1;
  Set < Integer > edges = new HashSet < Integer > ();
}
public class mColoring {
  static int possiblePaint(ArrayList < Node > nodes, int n, int m) {

    // Create a visited array of n nodes
    ArrayList < Integer > visited = new ArrayList < Integer > ();
    // initialized to zero
    for (int i = 0; i < n + 1; i++) {
      visited.add(0);
    }

    // maxColors used till now are 1 as
    // all nodes are painted color 1
    int maxColors = 1;

    // Do a full BFS traversal from
    // all unvisited starting points
    for (int sv = 1; sv <= n; sv++) {
      if (visited.get(sv) > 0) {
        continue;
      }

      // If the starting point is unvisited,
      // mark it visited and push it in queue
      visited.set(sv, 1);
      Queue < Integer > q = new LinkedList < > ();
      q.add(sv);

      // BFS Travel starts here
      while (q.size() != 0) {
        int top = q.peek();
        q.remove();

        // Checking all adjacent nodes
        // to "top" edge in our queue
        for (int it: nodes.get(top).edges) {

          // IMPORTANT: If the color of the
          // adjacent node is same, increase it by 1
          if (nodes.get(top).color == nodes.get(it).color) {
            nodes.get(it).color += 1;
          }

          // If number of colors used shoots m, return
          // 0
          maxColors = Math.max(maxColors,
            Math.max(nodes.get(top).color,
              nodes.get(it).color));
          if (maxColors > m)
            return 0;

          // If the adjacent node is not visited,
          // mark it visited and push it in queue
          if (visited.get(it) == 0) {
            visited.set(it, 1);
            q.remove(it);
          }
        }

      }
    }
    return 1;
  }

  // Driver code
  public static void main(String[] args) {
    int n = 4;
     int [][] graph = {{ 0, 1, 1, 1 },{ 1, 0, 1, 0 },{ 1, 1, 0, 1 },{ 1, 0, 1, 0 }};
    int m = 3; // Number of colors

    // Create a vector of n+1
    // nodes of type "node"
    // The zeroth position is just
    // dummy (1 to n to be used)
    ArrayList < Node > nodes = new ArrayList < Node > ();

    for (int i = 0; i < n + 1; i++) {
      nodes.add(new Node());
    }

    // Add edges to each node as per given input
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        if (graph[i][j] > 0) {
          // Connect the undirected graph
          nodes.get(i).edges.add(i);
          nodes.get(j).edges.add(j);
        }
      }
    }

    // Display answer
    int res = possiblePaint(nodes, n, m);
    if (res == 1) {
      System.out.println("It is possible to color all the nodes!!");
    } else {
      System.out.println("No solution exists!!");
    }
  }
}

Python Implementation

# Python Implementation
from queue import Queue

class node:
	
	color = 1
	edges = set()

def possiblePaint(nodes, n, m):

	# Create a visited array of n
	# nodes, initialized to zero
	visited = [0 for _ in range(n+1)]

	# maxColors used till now are 1 as
	# all nodes are painted color 1
	maxColors = 1

	# Do a full BFS traversal from
	# all unvisited starting points
	for _ in range(1, n + 1):
		if visited[_]:
			continue

		# If the starting point is unvisited,
		# mark it visited and push it in queue
		visited[_] = 1
		q = Queue()
		q.put(_)

		# BFS Travel starts here
		while not q.empty():
			top = q.get()

			# Checking all adjacent nodes
			# to "top" edge in our queue
			for _ in nodes[top].edges:

				# IMPORTANT: If the color of the
				# adjacent node is same, increase it by 1

				if nodes[top].color == nodes[_].color:
					nodes[_].color += 1

				# If number of colors used shoots m,
				# return 0
				maxColors = max(maxColors, max(
					nodes[top].color, nodes[_].color))
					
				if maxColors > m:
					print(maxColors)
					return 0

				# If the adjacent node is not visited,
				# mark it visited and push it in queue
				if not visited[_]:
					visited[_] = 1
					q.put(_)
					
	return 1

# Driver code
if __name__ == "__main__":
	
	n = 4
	graph = [ [ 0, 1, 1, 1 ],
			[ 1, 0, 1, 0 ],
			[ 1, 1, 0, 1 ],
			[ 1, 0, 1, 0 ] ]
	m = 3
	nodes = []
	for _ in range(n+1):
		nodes.append(node())

	# Add edges to each node as
	# per given input
	for _ in range(n):
		for __ in range(n):
			if graph[_][__]:
				
				# Connect the undirected graph
				nodes[_].edges.add(_)
				nodes[__].edges.add(__)

	# Display the final answer
	ans = possiblePaint(nodes, n, m)
	if(ans == 1):
	    print("It is possible to color all the nodes!!")
	else:
	    print("No solution exists!!")

 

Output

It is possible to color all the nodes!!

Complexity Analysis

Time Complexity: O(V + E).

Reason: The time complexity of BFS traversal is O(V). For every node, we are checking all its adjacent nodes, this will lead to the time complexity of O(V + E).

Space Complexity: O(V). 

Reason: The M_coloring function will require O(V) space for the visited array.

Frequently Asked Questions

What is Graph Coloring?

Graph coloring is an important problem of graph theory. In this problem, we'll be given a graph to color all of the vertices with the 'm' colors provided, ensuring that no two neighboring vertices have the same color.

What do you mean by the M-coloring decision problem?

The smallest number m for which the graph G can be colored is the M – colorability optimization problem. The integer is referred to as the graph's chromatic Number.

What are the applications of graph coloring?

There are various graph coloring applications, such as creating a schedule, register allocation, and Mobile Radio Frequency  Assignment.

Conclusion

In this article, we discussed the graph coloring problem and some approaches to how to solve it in detail. Graph coloring is one of the most frequently asked and important concepts in graph theory.

To get a clear insight into graph theory fundamentals, we suggest you go through this fantastic article. If you have any difficulty understanding the graph representation in the above-given approaches, here's a blog to get you acquainted with the types and representation of graphs.

Here are some recommended problems to test your understanding of graphs

Recommended Reading

Also, check out some more Problems, Courses for topics like DSA in C++, Guided Paths for topics like Data Structures and Algorithms, Interview Experiences, Interview Bundles, and Contests on Codestudio to give yourself an edge.

Happy Learning!

Was this article helpful ?
0 upvotes

Comments

No comments yet

Be the first to share what you think