Skip to content

Commit 57be79f

Browse files
author
lennart
committed
add getRoot, add forEachAncestor, edit Tests
1 parent ebcc878 commit 57be79f

2 files changed

Lines changed: 64 additions & 49 deletions

File tree

src/main/java/com/thealgorithms/tree/CentroidDecomposition.java

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,26 @@
33
import java.util.ArrayList;
44
import java.util.Arrays;
55
import java.util.List;
6-
import java.util.stream.Collector;
6+
import java.util.function.IntConsumer;
77
import java.util.stream.Collectors;
88

9+
/**
10+
* CentroidDecomposition builds the centroid decomposition of an undirected tree.
11+
*
12+
* <p>The algorithm recursively finds centroids, marks them as removed, and
13+
* constructs a centroid tree whose height is O(log n). This structure enables
14+
* efficient solutions for distance queries, nearest-colored-node queries, and
15+
* other divide-and-conquer algorithms on trees.</p>
16+
*
17+
* <p>Supports adding edges of the original tree, building the centroid tree,
18+
* and querying centroid parents, children, and internal state.
19+
* </p>
20+
*
21+
* <p><strong>Author:</strong> Lennart S.<br>
22+
* <strong>GitHub: https://github.com/lens161<br>
23+
* </p>
24+
*/
25+
926
public class CentroidDecomposition {
1027
private ArrayList<Integer>[] tree;
1128
private ArrayList<Integer>[] centroidTree;
@@ -28,6 +45,7 @@ public CentroidDecomposition(int n, int startingNode){
2845
if (startingNode < 0 || startingNode > n-1){
2946
throw new IllegalArgumentException("Starting node must be in range 0.." + (n - 1) + " but got " + startingNode);
3047
}
48+
this.startingNode = startingNode;
3149
for(int i = 0; i<n; i++){
3250
centroidParent[i] = -1;
3351
tree[i] = new ArrayList<>();
@@ -40,6 +58,7 @@ public CentroidDecomposition(int n){
4058
}
4159

4260
public void build(){
61+
reset();
4362
findCentroid(startingNode, startingNode);
4463
}
4564

@@ -86,6 +105,15 @@ public List<Integer> getCentroidChildren(int v) {
86105
.collect(Collectors.toList());
87106
}
88107

108+
public int getRoot() {
109+
for (int i = 0; i < N; i++) {
110+
if (centroidParent[i] == -1) {
111+
return i;
112+
}
113+
}
114+
throw new IllegalStateException("Centroid tree has no root. likely it was not built");
115+
}
116+
89117
public void findSubtreeSizes(int src){
90118
visited[src] = true;
91119
subtreeSizes[src] = 1;
@@ -130,43 +158,16 @@ public void findCentroid(int src, int previousCentroid){
130158
}
131159
}
132160

133-
public static void main(String[] args) {
134-
CentroidDecomposition cd = new CentroidDecomposition(16);
135-
cd.addEdgeTree(0, 1);
136-
cd.addEdgeTree(0, 2);
137-
cd.addEdgeTree(0, 3);
138-
cd.addEdgeTree(1, 4);
139-
cd.addEdgeTree(1, 5);
140-
cd.addEdgeTree(2, 6);
141-
cd.addEdgeTree(2, 7);
142-
cd.addEdgeTree(3, 8);
143-
cd.addEdgeTree(8, 9);
144-
cd.addEdgeTree(8, 10);
145-
cd.addEdgeTree(6, 11);
146-
cd.addEdgeTree(11, 12);
147-
cd.addEdgeTree(11, 13);
148-
cd.addEdgeTree(13, 14);
149-
cd.addEdgeTree(14, 15);
150-
151-
// boolean[] visited = new boolean[16];
152-
// int[] subtreeSizes = new int[16];
153-
154-
// int start = cd.startingNode;
155-
// int src = (int)(Math.random() * 15);
156-
// System.out.println("src= " + src);
157-
int start = cd.getStartingNode();
158-
cd.findCentroid(start, start);
159-
160-
// System.out.println((int)(Math.random() * 16));
161-
162-
for (int i = 0; i < cd.centroidTree.length; i++) {
163-
System.out.println(String.format("%s %s", i, cd.centroidTree[i]));
161+
/**
162+
* Applies the given action to all centroid ancestors of the given node,
163+
* including the node itself, walking up via centroidParent[] until the root.
164+
*/
165+
public void forEachAcestor(int centroid, IntConsumer action){
166+
int curr = centroid;
167+
while(curr != -1){
168+
action.accept(curr);
169+
curr = getParent(curr);
164170
}
165-
166-
// cd.findSubtreeSizes(8, visited, subtreeSizes);
167-
// for (int i = 0; i < subtreeSizes.length; i++) {
168-
// System.out.println(String.format("%s %s", i, subtreeSizes[i]));
169-
// }
170171
}
171172

172173
}

src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@
1111
import java.util.stream.Collectors;
1212

1313
import org.junit.jupiter.api.BeforeEach;
14-
import org.junit.jupiter.api.RepeatedTest;
1514
import org.junit.jupiter.api.Test;
1615

1716
class CentroidDecompositionTest {
1817
private CentroidDecomposition cd;
1918

2019
@BeforeEach
2120
void setUp(){
22-
cd = new CentroidDecomposition(16);
21+
cd = new CentroidDecomposition(16, 0);
2322
cd.addEdgeTree(0, 1);
2423
cd.addEdgeTree(0, 2);
2524
cd.addEdgeTree(0, 3);
@@ -54,7 +53,7 @@ void setUp(){
5453
15
5554
5655
57-
* centroid Tree:
56+
* centroid Tree (starting at 0):
5857
0
5958
/ | \
6059
1 11 8
@@ -176,23 +175,38 @@ void resetClearsCentroidData() {
176175
}
177176

178177
@Test
179-
void buildingFromDifferentStartNodesProducesSameParents() {
180-
// build from 0
178+
void buildingFromDifferentStartNodesYieldsValidTrees() {
181179
CentroidDecomposition a = new CentroidDecomposition(16, 0);
182180
copyEdges(cd, a);
183181
a.build();
182+
assertValidCentroidTree(a, 16);
184183

185-
// build from 7
186184
CentroidDecomposition b = new CentroidDecomposition(16, 7);
187185
copyEdges(cd, b);
188186
b.build();
187+
assertValidCentroidTree(b, 16);
188+
}
189+
190+
private static void assertValidCentroidTree(CentroidDecomposition cd, int n) {
191+
int roots = 0;
192+
int edges = 0;
189193

190-
// compare parent arrays node by node
191-
for (int v = 0; v < 16; v++) {
192-
assertEquals(a.getParent(v), b.getParent(v), "parent mismatch at node " + v);
194+
ArrayList<Integer>[] cg = cd.getCentroidTree();
195+
196+
for (int v = 0; v < n; v++) {
197+
if (cd.getParent(v) == -1) {
198+
roots++;
199+
}
200+
edges += cg[v].size();
193201
}
194-
}
195202

203+
// undirected edges counted twice
204+
edges /= 2;
205+
206+
assertEquals(1, roots, "must have exactly one root");
207+
assertEquals(n - 1, edges, "centroid tree must have n-1 edges");
208+
}
209+
196210
private static void copyEdges(CentroidDecomposition from, CentroidDecomposition to) {
197211
to.addEdgeTree(0, 1);
198212
to.addEdgeTree(0, 2);
@@ -211,7 +225,7 @@ private static void copyEdges(CentroidDecomposition from, CentroidDecomposition
211225
to.addEdgeTree(14, 15);
212226
}
213227

214-
@RepeatedTest(100)
228+
@Test
215229
void testBuildCentroidTree(){
216230
cd.build();
217231
ArrayList<Integer>[] centroidTree = cd.getCentroidTree();
@@ -231,7 +245,7 @@ void testBuildCentroidTree(){
231245
assertEquals(correctEight.get(j), centroidTree[8].get(j));
232246
}
233247

234-
for (int j = 0; j < centroidTree[8].size(); j++) {
248+
for (int j = 0; j < centroidTree[11].size(); j++) {
235249
assertEquals(correctEleven.get(j), centroidTree[11].get(j));
236250
}
237251

0 commit comments

Comments
 (0)