Graph coloring is a fundamental problem in graph theory and computer science, with applications ranging from scheduling to register allocation in compilers. In this tutorial, we will explore the concept of graph coloring, its algorithms, and real-world applications.
Graph coloring involves assigning colors to vertices of a graph such that no two adjacent vertices share the same color. The goal is often to use the minimum number of colors possible. This problem is NP-hard, meaning it's computationally intensive for large graphs, but there are several heuristic and exact algorithms to solve or approximate solutions.
The greedy algorithm is a simple heuristic for graph coloring. It colors vertices one by one and assigns the smallest possible color that has not been used by its adjacent vertices.
function greedyColoring(graph) {
const n = graph.length;
const result = new Array(n).fill(-1);
const availableColors = new Set();
for (let v = 0; v < n; v++) {
// Find all colors used by adjacent vertices
availableColors.clear();
for (let i = 0; i < graph[v].length; i++) {
if (graph[v][i] && result[i] !== -1) {
availableColors.add(result[i]);
}
}
// Assign the smallest color that is not used by adjacent vertices
for (let c = 0; c < n; c++) {
if (!availableColors.has(c)) {
result[v] = c;
break;
}
}
}
return result;
}
// Example usage:
const graph = [
[0, 1, 1, 1],
[1, 0, 1, 0],
[1, 1, 0, 1],
[1, 0, 1, 0]
];
console.log(greedyColoring(graph)); // Output: [0, 1, 2, 1]
Backtracking is an exact algorithm that explores all possible colorings and backtracks when a conflict is found.
function isSafe(v, graph, result, c) {
for (let i = 0; i < graph.length; i++) {
if (graph[v][i] && c === result[i]) {
return false;
}
}
return true;
}
function graphColoringUtil(graph, m, result, v) {
const n = graph.length;
if (v === n) {
return true;
}
for (let c = 0; c < m; c++) {
if (isSafe(v, graph, result, c)) {
result[v] = c;
if (graphColoringUtil(graph, m, result, v + 1)) {
return true;
}
result[v] = -1;
}
}
return false;
}
function graphColoring(graph, m) {
const n = graph.length;
const result = new Array(n).fill(-1);
if (!graphColoringUtil(graph, m, result, 0)) {
console.log("Solution does not exist");
return false;
}
console.log(result);
return true;
}
// Example usage:
const graph = [
[0, 1, 1, 1],
[1, 0, 1, 0],
[1, 1, 0, 1],
[1, 0, 1, 0]
];
graphColoring(graph, 3); // Output: [0, 1, 2, 1]
DSATUR (Degree of Saturation) is a heuristic that colors vertices based on the number of distinct colors used by their neighbors.
function dsaturColoring(graph) {
const n = graph.length;
const result = new Array(n).fill(-1);
const degree = new Array(n).fill(0);
const saturation = new Array(n).fill(0);
// Calculate initial degrees
for (let i = 0; i < n; i++) {
for (let j = 0; j < graph[i].length; j++) {
if (graph[i][j]) {
degree[i]++;
}
}
}
const queue = [];
for (let i = 0; i < n; i++) {
queue.push(i);
}
while (queue.length > 0) {
// Sort by saturation and then by degree
queue.sort((a, b) => {
if (saturation[a] === saturation[b]) {
return degree[b] - degree[a];
}
return saturation[b] - saturation[a];
});
const v = queue.shift();
const availableColors = new Set();
for (let i = 0; i < graph[v].length; i++) {
if (graph[v][i] && result[i] !== -1) {
availableColors.add(result[i]);
}
}
let minColor = 0;
while (availableColors.has(minColor)) {
minColor++;
}
result[v] = minColor;
for (let i = 0; i < graph[v].length; i++) {
if (graph[v][i]) {
saturation[i]++;
degree[i]--;
}
}
}
return result;
}
// Example usage:
const graph = [
[0, 1, 1, 1],
[1, 0, 1, 0],
[1, 1, 0, 1],
[1, 0, 1, 0]
];
console.log(dsaturColoring(graph)); // Output: [0, 1, 2, 1]
Graph coloring is a versatile and important problem with wide-ranging applications. By understanding different algorithms and their trade-offs, you can effectively solve graph coloring problems in various domains. Whether you're working on scheduling systems or optimizing compiler performance, mastering graph coloring techniques will be invaluable.