Skip to content

Commit 45dd8bb

Browse files
feat: Add Centroid Decomposition for trees (#7054)
- Implement CentroidDecomposition with O(N log N) construction - Add CentroidTree class with parent tracking and query methods - Include buildFromEdges helper for easy tree construction - Add comprehensive test suite with 20+ test cases - Cover edge cases, validation, and various tree structures Closes #7054
1 parent fba6292 commit 45dd8bb

File tree

2 files changed

+467
-0
lines changed

2 files changed

+467
-0
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package com.thealgorithms.tree;
2+
3+
import java.util.*;
4+
5+
/**
6+
* Centroid Decomposition is a divide-and-conquer technique for trees.
7+
* It recursively partitions a tree by finding centroids - nodes whose removal
8+
* creates balanced subtrees (each with at most N/2 nodes).
9+
*
10+
* Time Complexity: O(N log N) for construction
11+
* Space Complexity: O(N)
12+
*
13+
* Applications:
14+
* - Distance queries on trees
15+
* - Path counting problems
16+
* - Nearest neighbor searches
17+
*
18+
* @author lens161
19+
*/
20+
public final class CentroidDecomposition {
21+
22+
private CentroidDecomposition() {
23+
// Utility class
24+
}
25+
26+
/**
27+
* Represents the centroid tree structure.
28+
*/
29+
public static class CentroidTree {
30+
private final int n;
31+
private final List<List<Integer>> adj;
32+
private final int[] parent;
33+
private final int[] subtreeSize;
34+
private final boolean[] removed;
35+
private int root;
36+
37+
/**
38+
* Constructs a centroid tree from an adjacency list.
39+
*
40+
* @param adj adjacency list representation of the tree (0-indexed)
41+
* @throws IllegalArgumentException if tree is empty or null
42+
*/
43+
public CentroidTree(List<List<Integer>> adj) {
44+
if (adj == null || adj.isEmpty()) {
45+
throw new IllegalArgumentException("Tree cannot be empty or null");
46+
}
47+
48+
this.n = adj.size();
49+
this.adj = adj;
50+
this.parent = new int[n];
51+
this.subtreeSize = new int[n];
52+
this.removed = new boolean[n];
53+
Arrays.fill(parent, -1);
54+
55+
// Build centroid tree starting from node 0
56+
this.root = decompose(0, -1);
57+
}
58+
59+
/**
60+
* Recursively builds the centroid tree.
61+
*
62+
* @param u current node
63+
* @param p parent in centroid tree
64+
* @return centroid of current component
65+
*/
66+
private int decompose(int u, int p) {
67+
int size = getSubtreeSize(u, -1);
68+
int centroid = findCentroid(u, -1, size);
69+
70+
removed[centroid] = true;
71+
parent[centroid] = p;
72+
73+
// Recursively decompose each subtree
74+
for (int v : adj.get(centroid)) {
75+
if (!removed[v]) {
76+
decompose(v, centroid);
77+
}
78+
}
79+
80+
return centroid;
81+
}
82+
83+
/**
84+
* Calculates subtree size from node u.
85+
*
86+
* @param u current node
87+
* @param p parent node (-1 for root)
88+
* @return size of subtree rooted at u
89+
*/
90+
private int getSubtreeSize(int u, int p) {
91+
subtreeSize[u] = 1;
92+
for (int v : adj.get(u)) {
93+
if (v != p && !removed[v]) {
94+
subtreeSize[u] += getSubtreeSize(v, u);
95+
}
96+
}
97+
return subtreeSize[u];
98+
}
99+
100+
/**
101+
* Finds the centroid of a subtree.
102+
* A centroid is a node whose removal creates components with size ≤ totalSize/2.
103+
*
104+
* @param u current node
105+
* @param p parent node
106+
* @param totalSize total size of current component
107+
* @return centroid node
108+
*/
109+
private int findCentroid(int u, int p, int totalSize) {
110+
for (int v : adj.get(u)) {
111+
if (v != p && !removed[v] && subtreeSize[v] > totalSize / 2) {
112+
return findCentroid(v, u, totalSize);
113+
}
114+
}
115+
return u;
116+
}
117+
118+
/**
119+
* Gets the parent of a node in the centroid tree.
120+
*
121+
* @param node the node
122+
* @return parent node in centroid tree, or -1 if root
123+
*/
124+
public int getParent(int node) {
125+
if (node < 0 || node >= n) {
126+
throw new IllegalArgumentException("Invalid node: " + node);
127+
}
128+
return parent[node];
129+
}
130+
131+
/**
132+
* Gets the root of the centroid tree.
133+
*
134+
* @return root node
135+
*/
136+
public int getRoot() {
137+
return root;
138+
}
139+
140+
/**
141+
* Gets the number of nodes in the tree.
142+
*
143+
* @return number of nodes
144+
*/
145+
public int size() {
146+
return n;
147+
}
148+
149+
/**
150+
* Returns the centroid tree structure as a string.
151+
* Format: node -> parent (or ROOT for root node)
152+
*
153+
* @return string representation
154+
*/
155+
@Override
156+
public String toString() {
157+
StringBuilder sb = new StringBuilder("Centroid Tree:\n");
158+
for (int i = 0; i < n; i++) {
159+
sb.append("Node ").append(i).append(" -> ");
160+
if (parent[i] == -1) {
161+
sb.append("ROOT");
162+
} else {
163+
sb.append("Parent ").append(parent[i]);
164+
}
165+
sb.append("\n");
166+
}
167+
return sb.toString();
168+
}
169+
}
170+
171+
/**
172+
* Creates a centroid tree from an edge list.
173+
*
174+
* @param n number of nodes (0-indexed: 0 to n-1)
175+
* @param edges list of edges where each edge is [u, v]
176+
* @return CentroidTree object
177+
* @throws IllegalArgumentException if n <= 0 or edges is invalid
178+
*/
179+
public static CentroidTree buildFromEdges(int n, int[][] edges) {
180+
if (n <= 0) {
181+
throw new IllegalArgumentException("Number of nodes must be positive");
182+
}
183+
if (edges == null) {
184+
throw new IllegalArgumentException("Edges cannot be null");
185+
}
186+
if (edges.length != n - 1) {
187+
throw new IllegalArgumentException("Tree must have exactly n-1 edges");
188+
}
189+
190+
List<List<Integer>> adj = new ArrayList<>();
191+
for (int i = 0; i < n; i++) {
192+
adj.add(new ArrayList<>());
193+
}
194+
195+
for (int[] edge : edges) {
196+
if (edge.length != 2) {
197+
throw new IllegalArgumentException("Each edge must have exactly 2 nodes");
198+
}
199+
int u = edge[0];
200+
int v = edge[1];
201+
202+
if (u < 0 || u >= n || v < 0 || v >= n) {
203+
throw new IllegalArgumentException("Invalid node in edge: [" + u + ", " + v + "]");
204+
}
205+
206+
adj.get(u).add(v);
207+
adj.get(v).add(u);
208+
}
209+
210+
return new CentroidTree(adj);
211+
}
212+
}

0 commit comments

Comments
 (0)