org.jgrapht.alg.color.SaturationDegreeColoring Maven / Gradle / Ivy
/*
* (C) Copyright 2017-2023, by Dimitrios Michail and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.color;
import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.util.*;
import java.lang.reflect.*;
import java.util.*;
/**
* The Dsatur greedy coloring algorithm.
*
*
* This is the greedy coloring algorithm using saturation degree ordering. The saturation degree of
* a vertex is defined as the number of different colors to which it is adjacent. The algorithm
* selects always the vertex with the largest saturation degree. If multiple vertices have the same
* maximum saturation degree, a vertex of maximum degree in the uncolored subgraph is selected.
*
*
* Note that the DSatur is not optimal in general, but is optimal for bipartite graphs. Compared to
* other simpler greedy ordering heuristics, it is usually considered slower but more efficient
* w.r.t. the number of used colors. See the following papers for details:
*
* - D. Brelaz. New methods to color the vertices of a graph. Communications of ACM,
* 22(4):251–256, 1979.
* - The smallest hard-to-color graph for algorithm DSATUR. Discrete Mathematics, 236:151--165,
* 2001.
*
*
*
* This implementation requires $O(n^2)$ running time and space. The following paper discusses
* possible improvements in the running time.
*
* - J. S. Turner. Almost all $k$-colorable graphs are easy to color. Journal of Algorithms.
* 9(1):63--82, 1988.
*
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Dimitrios Michail
*/
public class SaturationDegreeColoring
implements VertexColoringAlgorithm
{
private final Graph graph;
/**
* Construct a new coloring algorithm.
*
* @param graph the input graph
*/
public SaturationDegreeColoring(Graph graph)
{
this.graph = Objects.requireNonNull(graph, "Graph cannot be null");
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public Coloring getColoring()
{
/*
* Initialize data structures
*/
int n = graph.vertexSet().size();
int maxColor = -1;
Map colors = CollectionUtil.newHashMapWithExpectedSize(n);
Map adjColors = CollectionUtil.newHashMapWithExpectedSize(n);
Map saturation = CollectionUtil.newHashMapWithExpectedSize(n);
/*
* Compute degrees, available colors, and maximum degree.
*/
int maxDegree = 0;
Map degree = CollectionUtil.newHashMapWithExpectedSize(n);
for (V v : graph.vertexSet()) {
int d = graph.edgesOf(v).size();
degree.put(v, d);
maxDegree = Math.max(maxDegree, d);
adjColors.put(v, new BitSet());
saturation.put(v, 0);
}
/*
* Initialize heap
*/
Heap heap = new Heap(n, new DSaturComparator(saturation, degree));
Map handles = new HashMap<>();
for (V v : graph.vertexSet()) {
handles.put(v, new HeapHandle(v));
}
heap.bulkInsert(
handles.values().toArray((HeapHandle[]) Array.newInstance(HeapHandle.class, 0)));
/*
* Color vertices
*/
while (heap.size() > 0) {
V v = heap.deleteMin().vertex;
// find first free color
BitSet used = adjColors.get(v);
int c = used.nextClearBit(0);
maxColor = Math.max(maxColor, c);
// color the vertex
colors.put(v, c);
// partial cleanup to save some space
adjColors.remove(v);
// update neighbors
for (E e : graph.edgesOf(v)) {
V u = Graphs.getOppositeVertex(graph, e, v);
if (!colors.containsKey(u)) {
// update used colors
int uSaturation = saturation.get(u);
BitSet uAdjColors = adjColors.get(u);
HeapHandle uHandle = handles.get(u);
if (uAdjColors.get(c)) {
// same saturation, degree decrease
// remove and reinsert
heap.delete(uHandle);
degree.put(u, degree.get(u) - 1);
heap.insert(uHandle);
} else {
// saturation increase, degree decrease
uAdjColors.set(c);
saturation.put(u, uSaturation + 1);
degree.put(u, degree.get(u) - 1);
// simple fix upwards inside heap since priority increased
heap.fixup(uHandle);
}
}
}
}
return new ColoringImpl<>(colors, maxColor + 1);
}
/*
* Special case comparator for the DSatur algorithm. Compares first by saturation and then by
* degree (maximum is better in both cases).
*/
private class DSaturComparator
implements Comparator
{
private Map saturation;
private Map degree;
public DSaturComparator(Map saturation, Map degree)
{
this.saturation = saturation;
this.degree = degree;
}
@Override
public int compare(V o1, V o2)
{
int sat1 = saturation.get(o1);
int sat2 = saturation.get(o2);
if (sat1 > sat2) {
return -1;
} else if (sat1 < sat2) {
return 1;
} else {
return -1 * Integer.compare(degree.get(o1), degree.get(o2));
}
}
}
/*
* An addressable heap handle.
*/
private class HeapHandle
{
int index;
V vertex;
public HeapHandle(V vertex)
{
this.vertex = vertex;
this.index = -1;
}
}
/*
* An addressable binary heap.
*
* No checks are performed (on purpose) for invalid handle use, or capacity violations.
*/
private class Heap
{
private Comparator comparator;
private int size;
private HeapHandle[] array;
@SuppressWarnings("unchecked")
public Heap(int capacity, Comparator comparator)
{
this.comparator = comparator;
this.size = 0;
this.array = (HeapHandle[]) Array.newInstance(HeapHandle.class, capacity + 1);
}
private void fixdown(int k)
{
HeapHandle h = array[k];
while (2 * k <= size) {
int j = 2 * k;
if (j < size && comparator.compare(array[j].vertex, array[j + 1].vertex) > 0) {
j++;
}
if (comparator.compare(h.vertex, array[j].vertex) <= 0) {
break;
}
array[k] = array[j];
array[k].index = k;
k = j;
}
array[k] = h;
h.index = k;
}
private void fixup(int k)
{
HeapHandle h = array[k];
while (k > 1 && comparator.compare(array[k / 2].vertex, h.vertex) > 0) {
array[k] = array[k / 2];
array[k].index = k;
k /= 2;
}
array[k] = h;
h.index = k;
}
private void forceFixup(int k)
{
HeapHandle h = array[k];
while (k > 1) {
array[k] = array[k / 2];
array[k].index = k;
k /= 2;
}
array[k] = h;
h.index = k;
}
public HeapHandle deleteMin()
{
HeapHandle result = array[1];
if (size == 1) {
array[1] = null;
size = 0;
} else {
array[1] = array[size];
array[size] = null;
size--;
fixdown(1);
}
result.index = -1;
return result;
}
public int size()
{
return size;
}
public void fixup(HeapHandle handle)
{
fixup(handle.index);
}
public void delete(HeapHandle handle)
{
forceFixup(handle.index);
deleteMin();
}
public void insert(HeapHandle handle)
{
size++;
array[size] = handle;
handle.index = size;
fixup(size);
}
public void bulkInsert(HeapHandle[] handles)
{
for (int i = 0; i < handles.length; i++) {
size++;
array[size] = handles[i];
handles[i].index = size;
}
for (int i = size / 2; i > 0; i--) {
fixdown(i);
}
}
}
}