forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
WelshPowell
(Graph Colouring) (TheAlgorithms#5034)
* Welsh Powell Algorithm + Test --------- Co-authored-by: Piotr Idzik <65706193+vil02@users.noreply.github.com>
- Loading branch information
Showing
2 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
113 changes: 113 additions & 0 deletions
113
src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
124 changes: 124 additions & 0 deletions
124
src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |