Skip to content

Commit fa8ea8e

Browse files
Feature/centroid decomposition (#7086)
* 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 * 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 * fix: Remove trailing whitespace from CentroidDecompositionTest * fix: Remove trailing whitespace and add newlines at end of files * fix: Format code to comply with clang-format and checkstyle requirements * style: Fix lambda formatting in test assertions - Change single-line lambdas to multi-line format - Align with repository code style guidelines * style: Apply clang-format to match repository style guide - Format code according to .clang-format configuration - Use single-line lambdas as allowed by AllowShortLambdasOnASingleLine: All - Apply 4-space indentation - Ensure proper line endings
1 parent e6c576c commit fa8ea8e

File tree

2 files changed

+453
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)