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