mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-10 21:43:15 +08:00
Add WelshPowell
(Graph Colouring) (#5034)
* Welsh Powell Algorithm + Test --------- Co-authored-by: Piotr Idzik <65706193+vil02@users.noreply.github.com>
This commit is contained in:
@ -0,0 +1,113 @@
|
|||||||
|
package com.thealgorithms.datastructures.graphs;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Welsh-Powell algorithm is a graph coloring algorithm
|
||||||
|
* used for coloring a graph with the minimum number of colors.
|
||||||
|
* https://en.wikipedia.org/wiki/Graph_coloring
|
||||||
|
*/
|
||||||
|
|
||||||
|
public final class WelshPowell {
|
||||||
|
private static final int BLANK_COLOR = -1; // Representing uncolored state
|
||||||
|
|
||||||
|
private WelshPowell() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Graph {
|
||||||
|
private HashSet<Integer>[] adjacencyLists;
|
||||||
|
|
||||||
|
private Graph(int vertices) {
|
||||||
|
if (vertices < 0) {
|
||||||
|
throw new IllegalArgumentException("Number of vertices cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
adjacencyLists = new HashSet[vertices];
|
||||||
|
Arrays.setAll(adjacencyLists, i -> new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEdge(int nodeA, int nodeB) {
|
||||||
|
validateVertex(nodeA);
|
||||||
|
validateVertex(nodeB);
|
||||||
|
if (nodeA == nodeB) {
|
||||||
|
throw new IllegalArgumentException("Self-loops are not allowed");
|
||||||
|
}
|
||||||
|
adjacencyLists[nodeA].add(nodeB);
|
||||||
|
adjacencyLists[nodeB].add(nodeA);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateVertex(int vertex) {
|
||||||
|
if (vertex < 0 || vertex >= getNumVertices()) {
|
||||||
|
throw new IllegalArgumentException("Vertex " + vertex + " is out of bounds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<Integer> getAdjacencyList(int vertex) {
|
||||||
|
return adjacencyLists[vertex];
|
||||||
|
}
|
||||||
|
|
||||||
|
int getNumVertices() {
|
||||||
|
return adjacencyLists.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Graph makeGraph(int numberOfVertices, int[][] listOfEdges) {
|
||||||
|
Graph graph = new Graph(numberOfVertices);
|
||||||
|
for (int[] edge : listOfEdges) {
|
||||||
|
if (edge.length != 2) {
|
||||||
|
throw new IllegalArgumentException("Edge array must have exactly two elements");
|
||||||
|
}
|
||||||
|
graph.addEdge(edge[0], edge[1]);
|
||||||
|
}
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] findColoring(Graph graph) {
|
||||||
|
int[] colors = initializeColors(graph.getNumVertices());
|
||||||
|
Integer[] sortedVertices = getSortedNodes(graph);
|
||||||
|
for (int vertex : sortedVertices) {
|
||||||
|
if (isBlank(colors[vertex])) {
|
||||||
|
boolean[] usedColors = computeUsedColors(graph, vertex, colors);
|
||||||
|
final var newColor = firstUnusedColor(usedColors);
|
||||||
|
colors[vertex] = newColor;
|
||||||
|
Arrays.stream(sortedVertices).forEach(otherVertex -> {
|
||||||
|
if (isBlank(colors[otherVertex]) && !isAdjacentToColored(graph, otherVertex, colors)) {
|
||||||
|
colors[otherVertex] = newColor;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBlank(int color) {
|
||||||
|
return color == BLANK_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAdjacentToColored(Graph graph, int vertex, int[] colors) {
|
||||||
|
return graph.getAdjacencyList(vertex).stream().anyMatch(otherVertex -> !isBlank(colors[otherVertex]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] initializeColors(int numberOfVertices) {
|
||||||
|
int[] colors = new int[numberOfVertices];
|
||||||
|
Arrays.fill(colors, BLANK_COLOR);
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer[] getSortedNodes(final Graph graph) {
|
||||||
|
return IntStream.range(0, graph.getNumVertices()).boxed().sorted(Comparator.comparingInt(v -> - graph.getAdjacencyList(v).size())).toArray(Integer[] ::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean[] computeUsedColors(final Graph graph, final int vertex, final int[] colors) {
|
||||||
|
boolean[] usedColors = new boolean[graph.getNumVertices()];
|
||||||
|
graph.getAdjacencyList(vertex).stream().map(neighbor -> colors[neighbor]).filter(color -> !isBlank(color)).forEach(color -> usedColors[color] = true);
|
||||||
|
return usedColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int firstUnusedColor(boolean[] usedColors) {
|
||||||
|
return IntStream.range(0, usedColors.length).filter(color -> !usedColors[color]).findFirst().getAsInt();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
package com.thealgorithms.datastructures.graphs;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.thealgorithms.datastructures.graphs.WelshPowell.Graph;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class WelshPowellTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimpleGraph() {
|
||||||
|
final var graph = WelshPowell.makeGraph(4, new int[][] {{0, 1}, {1, 2}, {2, 3}});
|
||||||
|
int[] colors = WelshPowell.findColoring(graph);
|
||||||
|
assertTrue(isColoringValid(graph, colors));
|
||||||
|
assertEquals(2, countDistinctColors(colors));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDisconnectedGraph() {
|
||||||
|
final var graph = WelshPowell.makeGraph(3, new int[][] {}); // No edges
|
||||||
|
int[] colors = WelshPowell.findColoring(graph);
|
||||||
|
assertTrue(isColoringValid(graph, colors));
|
||||||
|
assertEquals(1, countDistinctColors(colors));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompleteGraph() {
|
||||||
|
final var graph = WelshPowell.makeGraph(3, new int[][] {{0, 1}, {1, 2}, {2, 0}});
|
||||||
|
int[] colors = WelshPowell.findColoring(graph);
|
||||||
|
assertTrue(isColoringValid(graph, colors));
|
||||||
|
assertEquals(3, countDistinctColors(colors));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following test originates from the following website : https://www.geeksforgeeks.org/welsh-powell-graph-colouring-algorithm/
|
||||||
|
@Test
|
||||||
|
void testComplexGraph() {
|
||||||
|
int[][] edges = {
|
||||||
|
{0, 7}, // A-H
|
||||||
|
{0, 1}, // A-B
|
||||||
|
{1, 3}, // B-D
|
||||||
|
{2, 3}, // C-D
|
||||||
|
{3, 8}, // D-I
|
||||||
|
{3, 10}, // D-K
|
||||||
|
{4, 10}, // E-K
|
||||||
|
{4, 5}, // E-F
|
||||||
|
{5, 6}, // F-G
|
||||||
|
{6, 10}, // G-K
|
||||||
|
{6, 7}, // G-H
|
||||||
|
{7, 8}, // H-I
|
||||||
|
{7, 9}, // H-J
|
||||||
|
{7, 10}, // H-K
|
||||||
|
{8, 9}, // I-J
|
||||||
|
{9, 10}, // J-K
|
||||||
|
};
|
||||||
|
|
||||||
|
final var graph = WelshPowell.makeGraph(11, edges); // 11 vertices from A (0) to K (10)
|
||||||
|
int[] colors = WelshPowell.findColoring(graph);
|
||||||
|
|
||||||
|
assertTrue(isColoringValid(graph, colors), "The coloring should be valid with no adjacent vertices sharing the same color.");
|
||||||
|
assertEquals(3, countDistinctColors(colors), "The chromatic number of the graph should be 3.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNegativeVertices() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(-1, new int[][] {}); }, "Number of vertices cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSelfLoop() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(3, new int[][] {{0, 0}}); }, "Self-loops are not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidVertex() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(3, new int[][] {{0, 3}}); }, "Vertex out of bounds");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(3, new int[][] {{0, -1}}); }, "Vertex out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidEdgeArray() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(3, new int[][] {{0}}); }, "Edge array must have exactly two elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWithPreColoredVertex() {
|
||||||
|
// Create a linear graph with 4 vertices and edges connecting them in sequence
|
||||||
|
final var graph = WelshPowell.makeGraph(4, new int[][] {{0, 1}, {1, 2}, {2, 3}});
|
||||||
|
|
||||||
|
// Apply the Welsh-Powell coloring algorithm to the graph
|
||||||
|
int[] colors = WelshPowell.findColoring(graph);
|
||||||
|
|
||||||
|
// Validate that the coloring is correct (no two adjacent vertices have the same color)
|
||||||
|
assertTrue(isColoringValid(graph, colors));
|
||||||
|
|
||||||
|
// Check if the algorithm has used at least 2 colors (expected for a linear graph)
|
||||||
|
assertTrue(countDistinctColors(colors) >= 2);
|
||||||
|
|
||||||
|
// Verify that all vertices have been assigned a color
|
||||||
|
for (int color : colors) {
|
||||||
|
assertTrue(color >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isColoringValid(Graph graph, int[] colors) {
|
||||||
|
if (Arrays.stream(colors).anyMatch(n -> n < 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < graph.getNumVertices(); i++) {
|
||||||
|
for (int neighbor : graph.getAdjacencyList(i)) {
|
||||||
|
if (i != neighbor && colors[i] == colors[neighbor]) {
|
||||||
|
return false; // Adjacent vertices have the same color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; // No adjacent vertices share the same color
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countDistinctColors(int[] colors) {
|
||||||
|
return (int) Arrays.stream(colors).distinct().count();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user