Skip to content

Commit 21e8247

Browse files
authored
Merge branch 'master' into master
2 parents 8213586 + df6da47 commit 21e8247

File tree

5 files changed

+237
-13
lines changed

5 files changed

+237
-13
lines changed

DIRECTORY.md

+4
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,8 @@
723723
* [WordLadder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/WordLadder.java)
724724
* zigZagPattern
725725
* [ZigZagPattern](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java)
726+
* tree
727+
* [HeavyLightDecomposition](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/tree/HeavyLightDecomposition.java)
726728
* test
727729
* java
728730
* com
@@ -1367,3 +1369,5 @@
13671369
* [WordLadderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/WordLadderTest.java)
13681370
* zigZagPattern
13691371
* [ZigZagPatternTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java)
1372+
* tree
1373+
* [HeavyLightDecompositionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java)

pom.xml

+4-13
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<dependency>
2121
<groupId>org.junit</groupId>
2222
<artifactId>junit-bom</artifactId>
23-
<version>5.11.4</version>
23+
<version>5.12.0</version>
2424
<type>pom</type>
2525
<scope>import</scope>
2626
</dependency>
@@ -31,7 +31,6 @@
3131
<dependency>
3232
<groupId>org.junit.jupiter</groupId>
3333
<artifactId>junit-jupiter</artifactId>
34-
<version>5.11.4</version>
3534
<scope>test</scope>
3635
</dependency>
3736
<dependency>
@@ -46,14 +45,6 @@
4645
<version>5.15.2</version>
4746
<scope>test</scope>
4847
</dependency>
49-
50-
51-
<dependency>
52-
<groupId>org.junit.jupiter</groupId>
53-
<artifactId>junit-jupiter-api</artifactId>
54-
<version>5.11.4</version>
55-
<scope>test</scope>
56-
</dependency>
5748
<dependency>
5849
<groupId>org.apache.commons</groupId>
5950
<artifactId>commons-lang3</artifactId>
@@ -78,7 +69,7 @@
7869
<plugin>
7970
<groupId>org.apache.maven.plugins</groupId>
8071
<artifactId>maven-compiler-plugin</artifactId>
81-
<version>3.13.0</version>
72+
<version>3.14.0</version>
8273
<configuration>
8374
<source>21</source>
8475
<target>21</target>
@@ -125,14 +116,14 @@
125116
<dependency>
126117
<groupId>com.puppycrawl.tools</groupId>
127118
<artifactId>checkstyle</artifactId>
128-
<version>10.21.2</version>
119+
<version>10.21.3</version>
129120
</dependency>
130121
</dependencies>
131122
</plugin>
132123
<plugin>
133124
<groupId>com.github.spotbugs</groupId>
134125
<artifactId>spotbugs-maven-plugin</artifactId>
135-
<version>4.8.6.6</version>
126+
<version>4.9.1.0</version>
136127
<configuration>
137128
<excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>
138129
<includeTests>true</includeTests>

spotbugs-exclude.xml

+3
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@
8383
<Match>
8484
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" />
8585
</Match>
86+
<Match>
87+
<Bug pattern="AT_STALE_THREAD_WRITE_OF_PRIMITIVE" />
88+
</Match>
8689
<!-- fb-contrib -->
8790
<Match>
8891
<Bug pattern="LSC_LITERAL_STRING_COMPARISON" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package com.thealgorithms.tree;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
* Heavy-Light Decomposition (HLD) implementation in Java.
8+
* HLD is used to efficiently handle path queries on trees, such as maximum,
9+
* sum, or updates. It decomposes the tree into heavy and light chains,
10+
* enabling queries in O(log N) time.
11+
* Wikipedia Reference: https://en.wikipedia.org/wiki/Heavy-light_decomposition
12+
* Author: Nithin U.
13+
* Github: https://github.com/NithinU2802
14+
*/
15+
16+
public class HeavyLightDecomposition {
17+
private List<List<Integer>> tree;
18+
private int[] parent;
19+
private int[] depth;
20+
private int[] subtreeSize;
21+
private int[] chainHead;
22+
private int[] position;
23+
private int[] nodeValue;
24+
private int[] segmentTree;
25+
private int positionIndex;
26+
27+
public HeavyLightDecomposition(int n) {
28+
tree = new ArrayList<>();
29+
for (int i = 0; i <= n; i++) {
30+
tree.add(new ArrayList<>());
31+
}
32+
parent = new int[n + 1];
33+
depth = new int[n + 1];
34+
subtreeSize = new int[n + 1];
35+
chainHead = new int[n + 1];
36+
position = new int[n + 1];
37+
nodeValue = new int[n + 1];
38+
segmentTree = new int[4 * (n + 1)];
39+
for (int i = 0; i <= n; i++) {
40+
chainHead[i] = -1;
41+
}
42+
positionIndex = 0;
43+
}
44+
45+
public int getPosition(int index) {
46+
return position[index];
47+
}
48+
49+
public int getPositionIndex() {
50+
return positionIndex;
51+
}
52+
53+
public void addEdge(int u, int v) {
54+
tree.get(u).add(v);
55+
tree.get(v).add(u);
56+
}
57+
58+
private void dfsSize(int node, int parentNode) {
59+
parent[node] = parentNode;
60+
subtreeSize[node] = 1;
61+
for (int child : tree.get(node)) {
62+
if (child != parentNode) {
63+
depth[child] = depth[node] + 1;
64+
dfsSize(child, node);
65+
subtreeSize[node] += subtreeSize[child];
66+
}
67+
}
68+
}
69+
70+
private void decompose(int node, int head) {
71+
chainHead[node] = head;
72+
position[node] = positionIndex++;
73+
int heavyChild = -1;
74+
int maxSubtreeSize = -1;
75+
for (int child : tree.get(node)) {
76+
if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) {
77+
heavyChild = child;
78+
maxSubtreeSize = subtreeSize[child];
79+
}
80+
}
81+
if (heavyChild != -1) {
82+
decompose(heavyChild, head);
83+
}
84+
for (int child : tree.get(node)) {
85+
if (child != parent[node] && child != heavyChild) {
86+
decompose(child, child);
87+
}
88+
}
89+
}
90+
91+
private void buildSegmentTree(int node, int start, int end) {
92+
if (start == end) {
93+
segmentTree[node] = nodeValue[start];
94+
return;
95+
}
96+
int mid = (start + end) / 2;
97+
buildSegmentTree(2 * node, start, mid);
98+
buildSegmentTree(2 * node + 1, mid + 1, end);
99+
segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]);
100+
}
101+
102+
public void updateSegmentTree(int node, int start, int end, int index, int value) {
103+
if (start == end) {
104+
segmentTree[node] = value;
105+
return;
106+
}
107+
int mid = (start + end) / 2;
108+
if (index <= mid) {
109+
updateSegmentTree(2 * node, start, mid, index, value);
110+
} else {
111+
updateSegmentTree(2 * node + 1, mid + 1, end, index, value);
112+
}
113+
segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]);
114+
}
115+
116+
public int querySegmentTree(int node, int start, int end, int left, int right) {
117+
if (left > end || right < start) {
118+
return Integer.MIN_VALUE;
119+
}
120+
if (left <= start && end <= right) {
121+
return segmentTree[node];
122+
}
123+
int mid = (start + end) / 2;
124+
int leftQuery = querySegmentTree(2 * node, start, mid, left, right);
125+
int rightQuery = querySegmentTree(2 * node + 1, mid + 1, end, left, right);
126+
return Math.max(leftQuery, rightQuery);
127+
}
128+
129+
public int queryMaxInPath(int u, int v) {
130+
int result = Integer.MIN_VALUE;
131+
while (chainHead[u] != chainHead[v]) {
132+
if (depth[chainHead[u]] < depth[chainHead[v]]) {
133+
int temp = u;
134+
u = v;
135+
v = temp;
136+
}
137+
result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[chainHead[u]], position[u]));
138+
u = parent[chainHead[u]];
139+
}
140+
if (depth[u] > depth[v]) {
141+
int temp = u;
142+
u = v;
143+
v = temp;
144+
}
145+
result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[u], position[v]));
146+
return result;
147+
}
148+
149+
public void initialize(int root, int[] values) {
150+
dfsSize(root, -1);
151+
decompose(root, root);
152+
for (int i = 0; i < values.length; i++) {
153+
nodeValue[position[i]] = values[i];
154+
}
155+
buildSegmentTree(1, 0, positionIndex - 1);
156+
}
157+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.thealgorithms.tree;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
9+
class HeavyLightDecompositionTest {
10+
11+
private HeavyLightDecomposition hld;
12+
private final int[] values = {0, 10, 20, 30, 40, 50};
13+
14+
/**
15+
* Initializes the test environment with a predefined tree structure and values.
16+
*/
17+
@BeforeEach
18+
void setUp() {
19+
hld = new HeavyLightDecomposition(5);
20+
hld.addEdge(1, 2);
21+
hld.addEdge(1, 3);
22+
hld.addEdge(2, 4);
23+
hld.addEdge(2, 5);
24+
hld.initialize(1, values);
25+
}
26+
27+
/**
28+
* Verifies that the tree initializes successfully without errors.
29+
*/
30+
@Test
31+
void testBasicTreeInitialization() {
32+
assertTrue(true, "Basic tree structure initialized successfully");
33+
}
34+
35+
/**
36+
* Tests the maximum value query in the path between nodes.
37+
*/
38+
@Test
39+
void testQueryMaxInPath() {
40+
assertEquals(50, hld.queryMaxInPath(4, 5), "Max value in path (4,5) should be 50");
41+
assertEquals(30, hld.queryMaxInPath(3, 2), "Max value in path (3,2) should be 30");
42+
}
43+
44+
/**
45+
* Tests updating a node's value and ensuring it is reflected in queries.
46+
*/
47+
@Test
48+
void testUpdateNodeValue() {
49+
hld.updateSegmentTree(1, 0, hld.getPositionIndex() - 1, hld.getPosition(4), 100);
50+
assertEquals(100, hld.queryMaxInPath(4, 5), "Updated value should be reflected in query");
51+
}
52+
53+
/**
54+
* Tests the maximum value query in a skewed tree structure.
55+
*/
56+
@Test
57+
void testSkewedTreeMaxQuery() {
58+
assertEquals(40, hld.queryMaxInPath(1, 4), "Max value in skewed tree (1,4) should be 40");
59+
}
60+
61+
/**
62+
* Ensures query handles cases where u is a deeper node correctly.
63+
*/
64+
@Test
65+
void testDepthSwapInPathQuery() {
66+
assertEquals(50, hld.queryMaxInPath(5, 2), "Query should handle depth swap correctly");
67+
assertEquals(40, hld.queryMaxInPath(4, 1), "Query should handle swapped nodes correctly and return max value");
68+
}
69+
}

0 commit comments

Comments
 (0)