mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-04 15:27:36 +08:00
Add Heavy-Light Decomposition (HLD) (#6169)
This commit is contained in:
@ -723,6 +723,8 @@
|
||||
* [WordLadder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/WordLadder.java)
|
||||
* zigZagPattern
|
||||
* [ZigZagPattern](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java)
|
||||
* tree
|
||||
* [HeavyLightDecomposition](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/tree/HeavyLightDecomposition.java)
|
||||
* test
|
||||
* java
|
||||
* com
|
||||
@ -1367,3 +1369,5 @@
|
||||
* [WordLadderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/WordLadderTest.java)
|
||||
* zigZagPattern
|
||||
* [ZigZagPatternTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java)
|
||||
* tree
|
||||
* [HeavyLightDecompositionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java)
|
||||
|
@ -0,0 +1,157 @@
|
||||
package com.thealgorithms.tree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Heavy-Light Decomposition (HLD) implementation in Java.
|
||||
* HLD is used to efficiently handle path queries on trees, such as maximum,
|
||||
* sum, or updates. It decomposes the tree into heavy and light chains,
|
||||
* enabling queries in O(log N) time.
|
||||
* Wikipedia Reference: https://en.wikipedia.org/wiki/Heavy-light_decomposition
|
||||
* Author: Nithin U.
|
||||
* Github: https://github.com/NithinU2802
|
||||
*/
|
||||
|
||||
public class HeavyLightDecomposition {
|
||||
private List<List<Integer>> tree;
|
||||
private int[] parent;
|
||||
private int[] depth;
|
||||
private int[] subtreeSize;
|
||||
private int[] chainHead;
|
||||
private int[] position;
|
||||
private int[] nodeValue;
|
||||
private int[] segmentTree;
|
||||
private int positionIndex;
|
||||
|
||||
public HeavyLightDecomposition(int n) {
|
||||
tree = new ArrayList<>();
|
||||
for (int i = 0; i <= n; i++) {
|
||||
tree.add(new ArrayList<>());
|
||||
}
|
||||
parent = new int[n + 1];
|
||||
depth = new int[n + 1];
|
||||
subtreeSize = new int[n + 1];
|
||||
chainHead = new int[n + 1];
|
||||
position = new int[n + 1];
|
||||
nodeValue = new int[n + 1];
|
||||
segmentTree = new int[4 * (n + 1)];
|
||||
for (int i = 0; i <= n; i++) {
|
||||
chainHead[i] = -1;
|
||||
}
|
||||
positionIndex = 0;
|
||||
}
|
||||
|
||||
public int getPosition(int index) {
|
||||
return position[index];
|
||||
}
|
||||
|
||||
public int getPositionIndex() {
|
||||
return positionIndex;
|
||||
}
|
||||
|
||||
public void addEdge(int u, int v) {
|
||||
tree.get(u).add(v);
|
||||
tree.get(v).add(u);
|
||||
}
|
||||
|
||||
private void dfsSize(int node, int parentNode) {
|
||||
parent[node] = parentNode;
|
||||
subtreeSize[node] = 1;
|
||||
for (int child : tree.get(node)) {
|
||||
if (child != parentNode) {
|
||||
depth[child] = depth[node] + 1;
|
||||
dfsSize(child, node);
|
||||
subtreeSize[node] += subtreeSize[child];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decompose(int node, int head) {
|
||||
chainHead[node] = head;
|
||||
position[node] = positionIndex++;
|
||||
int heavyChild = -1;
|
||||
int maxSubtreeSize = -1;
|
||||
for (int child : tree.get(node)) {
|
||||
if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) {
|
||||
heavyChild = child;
|
||||
maxSubtreeSize = subtreeSize[child];
|
||||
}
|
||||
}
|
||||
if (heavyChild != -1) {
|
||||
decompose(heavyChild, head);
|
||||
}
|
||||
for (int child : tree.get(node)) {
|
||||
if (child != parent[node] && child != heavyChild) {
|
||||
decompose(child, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void buildSegmentTree(int node, int start, int end) {
|
||||
if (start == end) {
|
||||
segmentTree[node] = nodeValue[start];
|
||||
return;
|
||||
}
|
||||
int mid = (start + end) / 2;
|
||||
buildSegmentTree(2 * node, start, mid);
|
||||
buildSegmentTree(2 * node + 1, mid + 1, end);
|
||||
segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]);
|
||||
}
|
||||
|
||||
public void updateSegmentTree(int node, int start, int end, int index, int value) {
|
||||
if (start == end) {
|
||||
segmentTree[node] = value;
|
||||
return;
|
||||
}
|
||||
int mid = (start + end) / 2;
|
||||
if (index <= mid) {
|
||||
updateSegmentTree(2 * node, start, mid, index, value);
|
||||
} else {
|
||||
updateSegmentTree(2 * node + 1, mid + 1, end, index, value);
|
||||
}
|
||||
segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]);
|
||||
}
|
||||
|
||||
public int querySegmentTree(int node, int start, int end, int left, int right) {
|
||||
if (left > end || right < start) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
if (left <= start && end <= right) {
|
||||
return segmentTree[node];
|
||||
}
|
||||
int mid = (start + end) / 2;
|
||||
int leftQuery = querySegmentTree(2 * node, start, mid, left, right);
|
||||
int rightQuery = querySegmentTree(2 * node + 1, mid + 1, end, left, right);
|
||||
return Math.max(leftQuery, rightQuery);
|
||||
}
|
||||
|
||||
public int queryMaxInPath(int u, int v) {
|
||||
int result = Integer.MIN_VALUE;
|
||||
while (chainHead[u] != chainHead[v]) {
|
||||
if (depth[chainHead[u]] < depth[chainHead[v]]) {
|
||||
int temp = u;
|
||||
u = v;
|
||||
v = temp;
|
||||
}
|
||||
result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[chainHead[u]], position[u]));
|
||||
u = parent[chainHead[u]];
|
||||
}
|
||||
if (depth[u] > depth[v]) {
|
||||
int temp = u;
|
||||
u = v;
|
||||
v = temp;
|
||||
}
|
||||
result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[u], position[v]));
|
||||
return result;
|
||||
}
|
||||
|
||||
public void initialize(int root, int[] values) {
|
||||
dfsSize(root, -1);
|
||||
decompose(root, root);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
nodeValue[position[i]] = values[i];
|
||||
}
|
||||
buildSegmentTree(1, 0, positionIndex - 1);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.thealgorithms.tree;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class HeavyLightDecompositionTest {
|
||||
|
||||
private HeavyLightDecomposition hld;
|
||||
private final int[] values = {0, 10, 20, 30, 40, 50};
|
||||
|
||||
/**
|
||||
* Initializes the test environment with a predefined tree structure and values.
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
hld = new HeavyLightDecomposition(5);
|
||||
hld.addEdge(1, 2);
|
||||
hld.addEdge(1, 3);
|
||||
hld.addEdge(2, 4);
|
||||
hld.addEdge(2, 5);
|
||||
hld.initialize(1, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the tree initializes successfully without errors.
|
||||
*/
|
||||
@Test
|
||||
void testBasicTreeInitialization() {
|
||||
assertTrue(true, "Basic tree structure initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the maximum value query in the path between nodes.
|
||||
*/
|
||||
@Test
|
||||
void testQueryMaxInPath() {
|
||||
assertEquals(50, hld.queryMaxInPath(4, 5), "Max value in path (4,5) should be 50");
|
||||
assertEquals(30, hld.queryMaxInPath(3, 2), "Max value in path (3,2) should be 30");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating a node's value and ensuring it is reflected in queries.
|
||||
*/
|
||||
@Test
|
||||
void testUpdateNodeValue() {
|
||||
hld.updateSegmentTree(1, 0, hld.getPositionIndex() - 1, hld.getPosition(4), 100);
|
||||
assertEquals(100, hld.queryMaxInPath(4, 5), "Updated value should be reflected in query");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the maximum value query in a skewed tree structure.
|
||||
*/
|
||||
@Test
|
||||
void testSkewedTreeMaxQuery() {
|
||||
assertEquals(40, hld.queryMaxInPath(1, 4), "Max value in skewed tree (1,4) should be 40");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures query handles cases where u is a deeper node correctly.
|
||||
*/
|
||||
@Test
|
||||
void testDepthSwapInPathQuery() {
|
||||
assertEquals(50, hld.queryMaxInPath(5, 2), "Query should handle depth swap correctly");
|
||||
assertEquals(40, hld.queryMaxInPath(4, 1), "Query should handle swapped nodes correctly and return max value");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user