Add Edmonds Blossom Algorithm (#5471)

This commit is contained in:
Tarun Vishwakarma
2024-10-02 23:34:01 +05:30
committed by GitHub
parent 842ff5294f
commit e493eb2958
3 changed files with 372 additions and 0 deletions

View File

@ -107,6 +107,7 @@
* [ConnectedComponent](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/ConnectedComponent.java)
* [Cycles](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java)
* [DijkstraAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java)
* [EdmondsBlossomAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java)
* [FloydWarshall](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java)
* [FordFulkerson](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java)
* [Graphs](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Graphs.java)
@ -659,6 +660,7 @@
* graphs
* [BoruvkaAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java)
* [DijkstraAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java)
* [EdmondsBlossomAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java)
* [FordFulkersonTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java)
* [HamiltonianCycleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java)
* [KosarajuTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java)

View File

@ -0,0 +1,251 @@
package com.thealgorithms.datastructures.graphs;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* The EdmondsBlossomAlgorithm class implements Edmonds' Blossom Algorithm
* to find the maximum matching in a general graph. The algorithm efficiently
* handles cases where the graph contains odd-length cycles by contracting
* "blossoms" and finding augmenting paths.
*<p>
* <a href="https://stanford.edu/~rezab/classes/cme323/S16/projects_reports/shoemaker_vare.pdf">Documentation of Algorithm (Stanford University)</a>
* <p></p>
* <a href="https://en.wikipedia.org/wiki/Blossom_algorithm">Wikipedia Documentation</a>
*/
public final class EdmondsBlossomAlgorithm {
private EdmondsBlossomAlgorithm() {
}
private static final int UNMATCHED = -1; // Constant to represent unmatched vertices
/**
* Finds the maximum matching in a general graph (Edmonds Blossom Algorithm).
*
* @param edges A list of edges in the graph.
* @param vertexCount The number of vertices in the graph.
* @return A list of matched pairs of vertices.
*/
public static List<int[]> maximumMatching(List<int[]> edges, int vertexCount) {
List<List<Integer>> graph = new ArrayList<>(vertexCount);
// Initialize each vertex's adjacency list.
for (int i = 0; i < vertexCount; i++) {
graph.add(new ArrayList<>());
}
// Populate the graph with the edges
for (int[] edge : edges) {
int u = edge[0];
int v = edge[1];
graph.get(u).add(v);
graph.get(v).add(u);
}
// Initial matching array and auxiliary data structures
int[] match = new int[vertexCount];
Arrays.fill(match, UNMATCHED); // All vertices are initially unmatched
int[] parent = new int[vertexCount];
int[] base = new int[vertexCount];
boolean[] inBlossom = new boolean[vertexCount]; // Indicates if a vertex is part of a blossom
boolean[] inQueue = new boolean[vertexCount]; // Tracks vertices in the BFS queue
// Main logic for finding maximum matching
for (int u = 0; u < vertexCount; u++) {
if (match[u] == UNMATCHED) {
// BFS initialization
Arrays.fill(parent, UNMATCHED);
for (int i = 0; i < vertexCount; i++) {
base[i] = i; // Each vertex is its own base initially
}
Arrays.fill(inBlossom, false);
Arrays.fill(inQueue, false);
Queue<Integer> queue = new LinkedList<>();
queue.add(u);
inQueue[u] = true;
boolean augmentingPathFound = false;
// BFS to find augmenting paths
while (!queue.isEmpty() && !augmentingPathFound) {
int current = queue.poll(); // Use a different name for clarity
for (int y : graph.get(current)) {
// Skip if we are looking at the same edge as the current match
if (match[current] == y) {
continue;
}
if (base[current] == base[y]) {
continue; // Avoid self-loops
}
if (parent[y] == UNMATCHED) {
// Case 1: y is unmatched, we've found an augmenting path
if (match[y] == UNMATCHED) {
parent[y] = current;
augmentingPathFound = true;
updateMatching(match, parent, y); // Augment along this path
break;
}
// Case 2: y is matched, add y's match to the queue
int z = match[y];
parent[y] = current;
parent[z] = y;
if (!inQueue[z]) {
queue.add(z);
inQueue[z] = true;
}
} else {
// Case 3: Both x and y have a parent; check for a cycle/blossom
int baseU = findBase(base, parent, current, y);
if (baseU != UNMATCHED) {
contractBlossom(new BlossomData(new BlossomAuxData(queue, parent, base, inBlossom, match, inQueue), current, y, baseU));
}
}
}
}
}
}
// Create result list of matched pairs
List<int[]> matchingResult = new ArrayList<>();
for (int v = 0; v < vertexCount; v++) {
if (match[v] != UNMATCHED && v < match[v]) {
matchingResult.add(new int[] {v, match[v]});
}
}
return matchingResult;
}
/**
* Updates the matching along the augmenting path found.
*
* @param match The matching array.
* @param parent The parent array used during the BFS.
* @param u The starting node of the augmenting path.
*/
private static void updateMatching(int[] match, int[] parent, int u) {
while (u != UNMATCHED) {
int v = parent[u];
int next = match[v];
match[v] = u;
match[u] = v;
u = next;
}
}
/**
* Finds the base of a node in the blossom.
*
* @param base The base array.
* @param parent The parent array.
* @param u One end of the edge.
* @param v The other end of the edge.
* @return The base of the node or UNMATCHED.
*/
private static int findBase(int[] base, int[] parent, int u, int v) {
boolean[] visited = new boolean[base.length];
// Mark ancestors of u
int currentU = u;
while (true) {
currentU = base[currentU]; // Move assignment out of the condition
visited[currentU] = true;
if (parent[currentU] == UNMATCHED) {
break;
}
currentU = parent[currentU]; // Move assignment out of the condition
}
// Find the common ancestor of v
int currentV = v;
while (true) {
currentV = base[currentV]; // Move assignment out of the condition
if (visited[currentV]) {
return currentV;
}
currentV = parent[currentV]; // Move assignment out of the condition
}
}
/**
* Contracts a blossom and updates the base array.
*
* @param blossomData The data containing the parameters related to the blossom contraction.
*/
private static void contractBlossom(BlossomData blossomData) {
for (int x = blossomData.u; blossomData.auxData.base[x] != blossomData.lca; x = blossomData.auxData.parent[blossomData.auxData.match[x]]) {
int baseX = blossomData.auxData.base[x];
int matchBaseX = blossomData.auxData.base[blossomData.auxData.match[x]];
// Split the inner assignment into two separate assignments
blossomData.auxData.inBlossom[baseX] = true;
blossomData.auxData.inBlossom[matchBaseX] = true;
}
for (int x = blossomData.v; blossomData.auxData.base[x] != blossomData.lca; x = blossomData.auxData.parent[blossomData.auxData.match[x]]) {
int baseX = blossomData.auxData.base[x];
int matchBaseX = blossomData.auxData.base[blossomData.auxData.match[x]];
// Split the inner assignment into two separate assignments
blossomData.auxData.inBlossom[baseX] = true;
blossomData.auxData.inBlossom[matchBaseX] = true;
}
// Update the base for all marked vertices
for (int i = 0; i < blossomData.auxData.base.length; i++) {
if (blossomData.auxData.inBlossom[blossomData.auxData.base[i]]) {
blossomData.auxData.base[i] = blossomData.lca; // Contract to the lowest common ancestor
if (!blossomData.auxData.inQueue[i]) {
blossomData.auxData.queue.add(i); // Add to queue if not already present
blossomData.auxData.inQueue[i] = true;
}
}
}
}
/**
* Auxiliary data class to encapsulate common parameters for the blossom operations.
*/
static class BlossomAuxData {
Queue<Integer> queue; // Queue for BFS traversal
int[] parent; // Parent array to store the paths
int[] base; // Base array to track the base of each vertex
boolean[] inBlossom; // Flags to indicate if a vertex is in a blossom
int[] match; // Array to store matches for each vertex
boolean[] inQueue; // Flags to track vertices in the BFS queue
BlossomAuxData(Queue<Integer> queue, int[] parent, int[] base, boolean[] inBlossom, int[] match, boolean[] inQueue) {
this.queue = queue;
this.parent = parent;
this.base = base;
this.inBlossom = inBlossom;
this.match = match;
this.inQueue = inQueue;
}
}
/**
* BlossomData class with reduced parameters.
*/
static class BlossomData {
BlossomAuxData auxData; // Use the auxiliary data class
int u; // One vertex in the edge
int v; // Another vertex in the edge
int lca; // Lowest Common Ancestor
BlossomData(BlossomAuxData auxData, int u, int v, int lca) {
this.auxData = auxData;
this.u = u;
this.v = v;
this.lca = lca;
}
}
}

View File

@ -0,0 +1,119 @@
package com.thealgorithms.datastructures.graphs;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
/**
* Unit tests for the EdmondsBlossomAlgorithm class.
*
* These tests ensure that the Edmonds' Blossom Algorithm implementation
* works as expected for various graph structures, returning the correct
* maximum matching.
*/
public class EdmondsBlossomAlgorithmTest {
/**
* Helper method to convert a list of matching pairs into a sorted 2D array.
* Sorting ensures consistent ordering of pairs and vertices for easier comparison in tests.
*
* @param matching List of matched pairs returned by the algorithm.
* @return A sorted 2D array of matching pairs.
*/
private int[][] convertMatchingToArray(List<int[]> matching) {
// Convert the list of pairs into an array
int[][] result = matching.toArray(new int[0][]);
// Sort each individual pair for consistency
for (int[] pair : result) {
Arrays.sort(pair);
}
// Sort the array of pairs to ensure consistent order
Arrays.sort(result, (a, b) -> Integer.compare(a[0], b[0]));
return result;
}
/**
* Test Case 1: A triangle graph where vertices 0, 1, and 2 form a cycle.
* The expected maximum matching is a single pair (0, 1) or any equivalent pair from the cycle.
*/
@Test
public void testCase1() {
List<int[]> edges = Arrays.asList(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 0});
List<int[]> matching = EdmondsBlossomAlgorithm.maximumMatching(edges, 3);
int[][] expected = new int[][] {{0, 1}};
assertArrayEquals(expected, convertMatchingToArray(matching));
}
/**
* Test Case 2: A disconnected graph where vertices 0, 1, 2 form one component,
* and vertices 3, 4 form another. The expected maximum matching is two pairs:
* (0, 1) and (3, 4).
*/
@Test
public void testCase2() {
List<int[]> edges = Arrays.asList(new int[] {0, 1}, new int[] {1, 2}, new int[] {3, 4});
List<int[]> matching = EdmondsBlossomAlgorithm.maximumMatching(edges, 5);
int[][] expected = new int[][] {{0, 1}, {3, 4}};
assertArrayEquals(expected, convertMatchingToArray(matching));
}
/**
* Test Case 3: A cycle graph involving vertices 0, 1, 2, 3 forming a cycle,
* with an additional edge (4, 5) outside the cycle.
* The expected maximum matching is (0, 1) and (4, 5).
*/
@Test
public void testCase3() {
List<int[]> edges = Arrays.asList(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 3}, new int[] {3, 0}, new int[] {4, 5});
List<int[]> matching = EdmondsBlossomAlgorithm.maximumMatching(edges, 6);
// Updated expected output to include the maximum matching pairs
int[][] expected = new int[][] {{0, 1}, {2, 3}, {4, 5}};
assertArrayEquals(expected, convertMatchingToArray(matching));
}
/**
* Test Case 4: A graph with no edges.
* Since there are no edges, the expected matching is an empty set.
*/
@Test
public void testCaseNoMatching() {
List<int[]> edges = Collections.emptyList(); // No edges
List<int[]> matching = EdmondsBlossomAlgorithm.maximumMatching(edges, 3);
int[][] expected = new int[][] {}; // No pairs expected
assertArrayEquals(expected, convertMatchingToArray(matching));
}
/**
* Test Case 5: A more complex graph with multiple cycles and extra edges.
* This tests the algorithm's ability to handle larger, more intricate graphs.
* The expected matching is {{0, 1}, {2, 5}, {3, 4}}.
*/
@Test
public void testCaseLargeGraph() {
List<int[]> edges = Arrays.asList(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 3}, new int[] {3, 4}, new int[] {4, 5}, new int[] {5, 0}, new int[] {1, 4}, new int[] {2, 5});
List<int[]> matching = EdmondsBlossomAlgorithm.maximumMatching(edges, 6);
// Check if the size of the matching is correct (i.e., 3 pairs)
assertEquals(3, matching.size());
// Check that the result contains valid pairs (any order is fine)
// Valid maximum matchings could be {{0, 1}, {2, 5}, {3, 4}} or {{0, 1}, {2, 3}, {4, 5}}, etc.
int[][] possibleMatching1 = new int[][] {{0, 1}, {2, 5}, {3, 4}};
int[][] possibleMatching2 = new int[][] {{0, 1}, {2, 3}, {4, 5}};
int[][] result = convertMatchingToArray(matching);
// Assert that the result is one of the valid maximum matchings
assertTrue(Arrays.deepEquals(result, possibleMatching1) || Arrays.deepEquals(result, possibleMatching2));
}
}