Skip to content

Add Maximum Weighted Matching Algorithm for Trees #6184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 1, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.thealgorithms.datastructures.graphs;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class UndirectedAdjacencyListGraph {
private ArrayList<HashMap<Integer, Integer>> adjacencyList = new ArrayList<>();

/**
* Adds a new node to the graph by adding an empty HashMap for its neighbors.
* @return the index of the newly added node in the adjacency list
*/
public int addNode() {
adjacencyList.add(new HashMap<>());
return adjacencyList.size() - 1;
}

/**
* Adds an undirected edge between the origin node (@orig) and the destination node (@dest) with the specified weight.
* If the edge already exists, no changes are made.
* @param orig the index of the origin node
* @param dest the index of the destination node
* @param weight the weight of the edge between @orig and @dest
* @return true if the edge was successfully added, false if the edge already exists or if any node index is invalid
*/
public boolean addEdge(int orig, int dest, int weight) {
int numNodes = adjacencyList.size();
if (orig >= numNodes || dest >= numNodes || orig < 0 || dest < 0) {
return false;
}

if (adjacencyList.get(orig).containsKey(dest)) {
return false;
}

adjacencyList.get(orig).put(dest, weight);
adjacencyList.get(dest).put(orig, weight);
return true;
}

/**
* Returns the set of all adjacent nodes (neighbors) for the given node.
* @param node the index of the node whose neighbors are to be retrieved
* @return a HashSet containing the indices of all neighboring nodes
*/
public HashSet<Integer> getNeighbors(int node) {
return new HashSet<>(adjacencyList.get(node).keySet());
}

/**
* Returns the weight of the edge between the origin node (@orig) and the destination node (@dest).
* If no edge exists, returns null.
* @param orig the index of the origin node
* @param dest the index of the destination node
* @return the weight of the edge between @orig and @dest, or null if no edge exists
*/
public Integer getEdgeWeight(int orig, int dest) {
return adjacencyList.get(orig).getOrDefault(dest, null);
}

/**
* Returns the number of nodes currently in the graph.
* @return the number of nodes in the graph
*/
public int size() {
return adjacencyList.size();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.thealgorithms.dynamicprogramming;

import com.thealgorithms.datastructures.graphs.UndirectedAdjacencyListGraph;

/**
* This class implements the algorithm for calculating the maximum weighted matching in a tree.
* The tree is represented as an undirected graph with weighted edges.
*
* Problem Description:
* Given an undirected tree G = (V, E) with edge weights γ: E → N and a root r ∈ V,
* the goal is to find a maximum weight matching M ⊆ E such that no two edges in M
* share a common vertex. The sum of the weights of the edges in M, ∑ e∈M γ(e), should be maximized.
* For more Information: <a href="https://en.wikipedia.org/wiki/Matching_(graph_theory)">Matching (graph theory)</a>
*
* @author <a href="https://github.com/DenizAltunkapan">Deniz Altunkapan</a>
*/
public class TreeMatching {

private UndirectedAdjacencyListGraph graph;
private int[][] dp;

/**
* Constructor that initializes the graph and the DP table.
*
* @param graph The graph that represents the tree and is used for the matching algorithm.
*/
public TreeMatching(UndirectedAdjacencyListGraph graph) {
this.graph = graph;
this.dp = new int[graph.size()][2];
}

/**
* Calculates the maximum weighted matching for the tree, starting from the given root node.
*
* @param root The index of the root node of the tree.
* @param parent The index of the parent node (used for recursion).
* @return The maximum weighted matching for the tree, starting from the root node.
*
*/
public int getMaxMatching(int root, int parent) {
if (root < 0 || root >= graph.size()) {
throw new IllegalArgumentException("Invalid root: " + root);
}
maxMatching(root, parent);
return Math.max(dp[root][0], dp[root][1]);
}

/**
* Recursively computes the maximum weighted matching for a node, assuming that the node
* can either be included or excluded from the matching.
*
* @param node The index of the current node for which the matching is calculated.
* @param parent The index of the parent node (to avoid revisiting the parent node during recursion).
*/
private void maxMatching(int node, int parent) {
dp[node][0] = 0;
dp[node][1] = 0;

int sumWithoutEdge = 0;
for (int adjNode : graph.getNeighbors(node)) {
if (adjNode == parent) {
continue;
}
maxMatching(adjNode, node);
sumWithoutEdge += Math.max(dp[adjNode][0], dp[adjNode][1]);
}

dp[node][0] = sumWithoutEdge;

for (int adjNode : graph.getNeighbors(node)) {
if (adjNode == parent) {
continue;
}
int weight = graph.getEdgeWeight(node, adjNode);
dp[node][1] = Math.max(dp[node][1], sumWithoutEdge - Math.max(dp[adjNode][0], dp[adjNode][1]) + dp[adjNode][0] + weight);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.thealgorithms.dynamicprogramming;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.thealgorithms.datastructures.graphs.UndirectedAdjacencyListGraph;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class TreeMatchingTest {
UndirectedAdjacencyListGraph graph;

@BeforeEach
void setUp() {
graph = new UndirectedAdjacencyListGraph();
for (int i = 0; i < 14; i++) {
graph.addNode();
}
}

@Test
void testMaxMatchingForGeneralTree() {
graph.addEdge(0, 1, 20);
graph.addEdge(0, 2, 30);
graph.addEdge(1, 3, 40);
graph.addEdge(1, 4, 10);
graph.addEdge(2, 5, 20);
graph.addEdge(3, 6, 30);
graph.addEdge(3, 7, 30);
graph.addEdge(5, 8, 40);
graph.addEdge(5, 9, 10);

TreeMatching treeMatching = new TreeMatching(graph);
assertEquals(110, treeMatching.getMaxMatching(0, -1));
}

@Test
void testMaxMatchingForBalancedTree() {
graph.addEdge(0, 1, 20);
graph.addEdge(0, 2, 30);
graph.addEdge(0, 3, 40);
graph.addEdge(1, 4, 10);
graph.addEdge(1, 5, 20);
graph.addEdge(2, 6, 20);
graph.addEdge(3, 7, 30);
graph.addEdge(5, 8, 10);
graph.addEdge(5, 9, 20);
graph.addEdge(7, 10, 10);
graph.addEdge(7, 11, 10);
graph.addEdge(7, 12, 5);
TreeMatching treeMatching = new TreeMatching(graph);
assertEquals(100, treeMatching.getMaxMatching(0, -1));
}

@Test
void testMaxMatchingForTreeWithVariedEdgeWeights() {
graph.addEdge(0, 1, 20);
graph.addEdge(0, 2, 30);
graph.addEdge(0, 3, 40);
graph.addEdge(0, 4, 50);
graph.addEdge(1, 5, 20);
graph.addEdge(2, 6, 20);
graph.addEdge(3, 7, 30);
graph.addEdge(5, 8, 10);
graph.addEdge(5, 9, 20);
graph.addEdge(7, 10, 10);
graph.addEdge(4, 11, 50);
graph.addEdge(4, 12, 20);
TreeMatching treeMatching = new TreeMatching(graph);
assertEquals(140, treeMatching.getMaxMatching(0, -1));
}

@Test
void emptyTree() {
TreeMatching treeMatching = new TreeMatching(graph);
assertEquals(0, treeMatching.getMaxMatching(0, -1));
}

@Test
void testSingleNodeTree() {
UndirectedAdjacencyListGraph singleNodeGraph = new UndirectedAdjacencyListGraph();
singleNodeGraph.addNode();

TreeMatching treeMatching = new TreeMatching(singleNodeGraph);
assertEquals(0, treeMatching.getMaxMatching(0, -1));
}

@Test
void testLinearTree() {
graph.addEdge(0, 1, 10);
graph.addEdge(1, 2, 20);
graph.addEdge(2, 3, 30);
graph.addEdge(3, 4, 40);

TreeMatching treeMatching = new TreeMatching(graph);
assertEquals(60, treeMatching.getMaxMatching(0, -1));
}

@Test
void testStarShapedTree() {
graph.addEdge(0, 1, 15);
graph.addEdge(0, 2, 25);
graph.addEdge(0, 3, 35);
graph.addEdge(0, 4, 45);

TreeMatching treeMatching = new TreeMatching(graph);
assertEquals(45, treeMatching.getMaxMatching(0, -1));
}

@Test
void testUnbalancedTree() {
graph.addEdge(0, 1, 10);
graph.addEdge(0, 2, 20);
graph.addEdge(1, 3, 30);
graph.addEdge(2, 4, 40);
graph.addEdge(4, 5, 50);

TreeMatching treeMatching = new TreeMatching(graph);
assertEquals(100, treeMatching.getMaxMatching(0, -1));
}
}