Skip to content

Commit 47a9b1b

Browse files
straf10vil02
andauthored
Add WelshPowell (Graph Colouring) (#5034)
* Welsh Powell Algorithm + Test --------- Co-authored-by: Piotr Idzik <[email protected]>
1 parent 55cc562 commit 47a9b1b

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.thealgorithms.datastructures.graphs;
2+
3+
import java.util.Arrays;
4+
import java.util.Comparator;
5+
import java.util.HashSet;
6+
import java.util.stream.IntStream;
7+
8+
/*
9+
* The Welsh-Powell algorithm is a graph coloring algorithm
10+
* used for coloring a graph with the minimum number of colors.
11+
* https://en.wikipedia.org/wiki/Graph_coloring
12+
*/
13+
14+
public final class WelshPowell {
15+
private static final int BLANK_COLOR = -1; // Representing uncolored state
16+
17+
private WelshPowell() {
18+
}
19+
20+
static class Graph {
21+
private HashSet<Integer>[] adjacencyLists;
22+
23+
private Graph(int vertices) {
24+
if (vertices < 0) {
25+
throw new IllegalArgumentException("Number of vertices cannot be negative");
26+
}
27+
28+
adjacencyLists = new HashSet[vertices];
29+
Arrays.setAll(adjacencyLists, i -> new HashSet<>());
30+
}
31+
32+
private void addEdge(int nodeA, int nodeB) {
33+
validateVertex(nodeA);
34+
validateVertex(nodeB);
35+
if (nodeA == nodeB) {
36+
throw new IllegalArgumentException("Self-loops are not allowed");
37+
}
38+
adjacencyLists[nodeA].add(nodeB);
39+
adjacencyLists[nodeB].add(nodeA);
40+
}
41+
42+
private void validateVertex(int vertex) {
43+
if (vertex < 0 || vertex >= getNumVertices()) {
44+
throw new IllegalArgumentException("Vertex " + vertex + " is out of bounds");
45+
}
46+
}
47+
48+
HashSet<Integer> getAdjacencyList(int vertex) {
49+
return adjacencyLists[vertex];
50+
}
51+
52+
int getNumVertices() {
53+
return adjacencyLists.length;
54+
}
55+
}
56+
57+
public static Graph makeGraph(int numberOfVertices, int[][] listOfEdges) {
58+
Graph graph = new Graph(numberOfVertices);
59+
for (int[] edge : listOfEdges) {
60+
if (edge.length != 2) {
61+
throw new IllegalArgumentException("Edge array must have exactly two elements");
62+
}
63+
graph.addEdge(edge[0], edge[1]);
64+
}
65+
return graph;
66+
}
67+
68+
public static int[] findColoring(Graph graph) {
69+
int[] colors = initializeColors(graph.getNumVertices());
70+
Integer[] sortedVertices = getSortedNodes(graph);
71+
for (int vertex : sortedVertices) {
72+
if (isBlank(colors[vertex])) {
73+
boolean[] usedColors = computeUsedColors(graph, vertex, colors);
74+
final var newColor = firstUnusedColor(usedColors);
75+
colors[vertex] = newColor;
76+
Arrays.stream(sortedVertices).forEach(otherVertex -> {
77+
if (isBlank(colors[otherVertex]) && !isAdjacentToColored(graph, otherVertex, colors)) {
78+
colors[otherVertex] = newColor;
79+
}
80+
});
81+
}
82+
}
83+
return colors;
84+
}
85+
86+
private static boolean isBlank(int color) {
87+
return color == BLANK_COLOR;
88+
}
89+
90+
private static boolean isAdjacentToColored(Graph graph, int vertex, int[] colors) {
91+
return graph.getAdjacencyList(vertex).stream().anyMatch(otherVertex -> !isBlank(colors[otherVertex]));
92+
}
93+
94+
private static int[] initializeColors(int numberOfVertices) {
95+
int[] colors = new int[numberOfVertices];
96+
Arrays.fill(colors, BLANK_COLOR);
97+
return colors;
98+
}
99+
100+
private static Integer[] getSortedNodes(final Graph graph) {
101+
return IntStream.range(0, graph.getNumVertices()).boxed().sorted(Comparator.comparingInt(v -> - graph.getAdjacencyList(v).size())).toArray(Integer[] ::new);
102+
}
103+
104+
private static boolean[] computeUsedColors(final Graph graph, final int vertex, final int[] colors) {
105+
boolean[] usedColors = new boolean[graph.getNumVertices()];
106+
graph.getAdjacencyList(vertex).stream().map(neighbor -> colors[neighbor]).filter(color -> !isBlank(color)).forEach(color -> usedColors[color] = true);
107+
return usedColors;
108+
}
109+
110+
private static int firstUnusedColor(boolean[] usedColors) {
111+
return IntStream.range(0, usedColors.length).filter(color -> !usedColors[color]).findFirst().getAsInt();
112+
}
113+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package com.thealgorithms.datastructures.graphs;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import com.thealgorithms.datastructures.graphs.WelshPowell.Graph;
8+
import java.util.Arrays;
9+
import org.junit.jupiter.api.Test;
10+
11+
class WelshPowellTest {
12+
13+
@Test
14+
void testSimpleGraph() {
15+
final var graph = WelshPowell.makeGraph(4, new int[][] {{0, 1}, {1, 2}, {2, 3}});
16+
int[] colors = WelshPowell.findColoring(graph);
17+
assertTrue(isColoringValid(graph, colors));
18+
assertEquals(2, countDistinctColors(colors));
19+
}
20+
21+
@Test
22+
void testDisconnectedGraph() {
23+
final var graph = WelshPowell.makeGraph(3, new int[][] {}); // No edges
24+
int[] colors = WelshPowell.findColoring(graph);
25+
assertTrue(isColoringValid(graph, colors));
26+
assertEquals(1, countDistinctColors(colors));
27+
}
28+
29+
@Test
30+
void testCompleteGraph() {
31+
final var graph = WelshPowell.makeGraph(3, new int[][] {{0, 1}, {1, 2}, {2, 0}});
32+
int[] colors = WelshPowell.findColoring(graph);
33+
assertTrue(isColoringValid(graph, colors));
34+
assertEquals(3, countDistinctColors(colors));
35+
}
36+
37+
// The following test originates from the following website : https://www.geeksforgeeks.org/welsh-powell-graph-colouring-algorithm/
38+
@Test
39+
void testComplexGraph() {
40+
int[][] edges = {
41+
{0, 7}, // A-H
42+
{0, 1}, // A-B
43+
{1, 3}, // B-D
44+
{2, 3}, // C-D
45+
{3, 8}, // D-I
46+
{3, 10}, // D-K
47+
{4, 10}, // E-K
48+
{4, 5}, // E-F
49+
{5, 6}, // F-G
50+
{6, 10}, // G-K
51+
{6, 7}, // G-H
52+
{7, 8}, // H-I
53+
{7, 9}, // H-J
54+
{7, 10}, // H-K
55+
{8, 9}, // I-J
56+
{9, 10}, // J-K
57+
};
58+
59+
final var graph = WelshPowell.makeGraph(11, edges); // 11 vertices from A (0) to K (10)
60+
int[] colors = WelshPowell.findColoring(graph);
61+
62+
assertTrue(isColoringValid(graph, colors), "The coloring should be valid with no adjacent vertices sharing the same color.");
63+
assertEquals(3, countDistinctColors(colors), "The chromatic number of the graph should be 3.");
64+
}
65+
66+
@Test
67+
void testNegativeVertices() {
68+
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(-1, new int[][] {}); }, "Number of vertices cannot be negative");
69+
}
70+
71+
@Test
72+
void testSelfLoop() {
73+
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(3, new int[][] {{0, 0}}); }, "Self-loops are not allowed");
74+
}
75+
76+
@Test
77+
void testInvalidVertex() {
78+
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(3, new int[][] {{0, 3}}); }, "Vertex out of bounds");
79+
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(3, new int[][] {{0, -1}}); }, "Vertex out of bounds");
80+
}
81+
82+
@Test
83+
void testInvalidEdgeArray() {
84+
assertThrows(IllegalArgumentException.class, () -> { WelshPowell.makeGraph(3, new int[][] {{0}}); }, "Edge array must have exactly two elements");
85+
}
86+
87+
@Test
88+
void testWithPreColoredVertex() {
89+
// Create a linear graph with 4 vertices and edges connecting them in sequence
90+
final var graph = WelshPowell.makeGraph(4, new int[][] {{0, 1}, {1, 2}, {2, 3}});
91+
92+
// Apply the Welsh-Powell coloring algorithm to the graph
93+
int[] colors = WelshPowell.findColoring(graph);
94+
95+
// Validate that the coloring is correct (no two adjacent vertices have the same color)
96+
assertTrue(isColoringValid(graph, colors));
97+
98+
// Check if the algorithm has used at least 2 colors (expected for a linear graph)
99+
assertTrue(countDistinctColors(colors) >= 2);
100+
101+
// Verify that all vertices have been assigned a color
102+
for (int color : colors) {
103+
assertTrue(color >= 0);
104+
}
105+
}
106+
107+
private boolean isColoringValid(Graph graph, int[] colors) {
108+
if (Arrays.stream(colors).anyMatch(n -> n < 0)) {
109+
return false;
110+
}
111+
for (int i = 0; i < graph.getNumVertices(); i++) {
112+
for (int neighbor : graph.getAdjacencyList(i)) {
113+
if (i != neighbor && colors[i] == colors[neighbor]) {
114+
return false; // Adjacent vertices have the same color
115+
}
116+
}
117+
}
118+
return true; // No adjacent vertices share the same color
119+
}
120+
121+
private int countDistinctColors(int[] colors) {
122+
return (int) Arrays.stream(colors).distinct().count();
123+
}
124+
}

0 commit comments

Comments
 (0)