From 563ef6837f3d535b9071f1293d4757709f378968 Mon Sep 17 00:00:00 2001 From: lennart Date: Mon, 10 Nov 2025 17:26:40 +0100 Subject: [PATCH 01/18] make base structure, make method for finding sizes of subtrees --- .../tree/CentroidDecomposition.java | 87 +++++++++++++++++++ .../tree/CentroidDecompositionTest.java | 64 ++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 src/main/java/com/thealgorithms/tree/CentroidDecomposition.java create mode 100644 src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java new file mode 100644 index 000000000000..6dd2defa7449 --- /dev/null +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -0,0 +1,87 @@ +package com.thealgorithms.tree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.HashMap; + +public class CentroidDecomposition { + private List[] tree; + private List[] centroidTree; + private boolean[] removedCentroids; + + public CentroidDecomposition(int n){ + tree = new ArrayList[n]; + centroidTree = new ArrayList[n]; + removedCentroids = new boolean[n]; + for(int i = 0; i(); + } + } + + public void addEdge(int u, int v){ + tree[u].add(v); + tree[v].add(u); + } + + public void findSubtreeSizes(int src, boolean[] visited, int[] subtreeSizes){ + // dfs traversal to find size of subtree rooted at src + visited[src] = true; + subtreeSizes[src] = 1; + for (int node : tree[src]){ + if(!visited[node]){ + visited[node] = true; + findSubtreeSizes(node, visited, subtreeSizes); // recurse down to last child node + subtreeSizes[src] += subtreeSizes[node]; // add size of full recursive path to subtree Size of src + } + } + } + + private void findCentroid(int src){ + int treeSize = tree[src].size(); + int[] subtreeSizes = new int[treeSize]; + boolean[] visited = new boolean[treeSize]; + Arrays.fill(visited, false); + + findSubtreeSizes(src, visited, subtreeSizes); + + boolean isCentroid = true; + for (int node : tree[src]){ + isCentroid = (subtreeSizes[node] <= (subtreeSizes[src]/2)) ? true : false; + } + + if (isCentroid){ + for (int node : tree[src]){ + + } + } + else{ + // pick one of the children of the node for next check + } + } + + public static void main(String[] args) { + CentroidDecomposition cd = new CentroidDecomposition(16); + cd.addEdge(0, 1); + cd.addEdge(0, 2); + cd.addEdge(0, 3); + cd.addEdge(1, 4); + cd.addEdge(1, 5); + cd.addEdge(2, 6); + cd.addEdge(2, 7); + cd.addEdge(3, 8); + cd.addEdge(8, 9); + cd.addEdge(8, 10); + cd.addEdge(6, 11); + cd.addEdge(11, 12); + cd.addEdge(11, 13); + cd.addEdge(13, 14); + cd.addEdge(14, 15); + + boolean[] visited = new boolean[16]; + int[] subtreeSizes = new int[16]; + + cd.findSubtreeSizes(3, visited, subtreeSizes); + } + +} diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java new file mode 100644 index 000000000000..2f74d4bc0943 --- /dev/null +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -0,0 +1,64 @@ +package com.thealgorithms.tree; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Answers.values; + +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CentroidDecompositionTest { + private CentroidDecomposition cd; + + @BeforeEach + void setUp(){ + cd = new CentroidDecomposition(16); + cd.addEdge(0, 1); + cd.addEdge(0, 2); + cd.addEdge(0, 3); + cd.addEdge(1, 4); + cd.addEdge(1, 5); + cd.addEdge(2, 6); + cd.addEdge(2, 7); + cd.addEdge(3, 8); + cd.addEdge(8, 9); + cd.addEdge(8, 10); + cd.addEdge(6, 11); + cd.addEdge(11, 12); + cd.addEdge(11, 13); + cd.addEdge(13, 14); + cd.addEdge(14, 15); + + /* + * 0 + / | \ + 1 2 3 + / \ / \ \ + 4 5 6 7 8 + | / \ + 11 9 10 + / \ + 12 13 + \ + 14 + \ + 15 + + */ + } + + @Test + void testFindSubtreeSizes(){ + boolean[] visited = new boolean[16]; + int[] subtreeSizes = new int[16]; + + cd.findSubtreeSizes(3, visited, subtreeSizes); + assertEquals(subtreeSizes[8], 3); + assertEquals(subtreeSizes[0], 12); + } + + + +} From 3474d602b8aae4e1754c11972125f32c1c9e415c Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 12 Nov 2025 17:05:43 +0100 Subject: [PATCH 02/18] add findCentroid recursive method, add tests for building CTree --- .../tree/CentroidDecomposition.java | 93 +++++++++++++++---- .../tree/CentroidDecompositionTest.java | 34 ++++++- 2 files changed, 108 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 6dd2defa7449..077f0f677694 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -3,33 +3,64 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.HashMap; public class CentroidDecomposition { private List[] tree; private List[] centroidTree; - private boolean[] removedCentroids; + private boolean[] centroidMarked; + private int[] centroidParent; + private int startingNode; + private int N; + public CentroidDecomposition(int n, int startingNode){ + tree = new ArrayList[n]; + centroidTree = new ArrayList[n]; + N = n; + for (int i = 0; i < centroidTree.length; i++) centroidTree[i] = new ArrayList<>(); + for (int i = 0; i < tree.length; i++) tree[i] = new ArrayList<>(); + centroidMarked = new boolean[n]; + centroidParent = new int[n]; + startingNode = (int)(Math.random() * n+1); + for(int i = 0; i(); + } + } public CentroidDecomposition(int n){ tree = new ArrayList[n]; centroidTree = new ArrayList[n]; - removedCentroids = new boolean[n]; + centroidMarked = new boolean[n]; + centroidParent = new int[n]; + startingNode = (this.startingNode == -1) ? (int)(Math.random() * n+1) : this.startingNode; + N = n; + for (int i = 0; i < centroidTree.length; i++) centroidTree[i] = new ArrayList<>(); + for (int i = 0; i < tree.length; i++) tree[i] = new ArrayList<>(); for(int i = 0; i(); } } + public List[] getCentroidTree(){ + return centroidTree; + } + public void addEdge(int u, int v){ tree[u].add(v); tree[v].add(u); } + private void addEdgeCTree(int u, int v){ + centroidTree[u].add(v); + centroidTree[v].add(u); + centroidParent[v] = u; + } + + public void findSubtreeSizes(int src, boolean[] visited, int[] subtreeSizes){ // dfs traversal to find size of subtree rooted at src visited[src] = true; subtreeSizes[src] = 1; for (int node : tree[src]){ - if(!visited[node]){ + if(!visited[node] && !centroidMarked[node]){ visited[node] = true; findSubtreeSizes(node, visited, subtreeSizes); // recurse down to last child node subtreeSizes[src] += subtreeSizes[node]; // add size of full recursive path to subtree Size of src @@ -37,31 +68,44 @@ public void findSubtreeSizes(int src, boolean[] visited, int[] subtreeSizes){ } } - private void findCentroid(int src){ - int treeSize = tree[src].size(); - int[] subtreeSizes = new int[treeSize]; - boolean[] visited = new boolean[treeSize]; + public void findCentroid(int src, int previousCentroid){ + int[] subtreeSizes = new int[N]; + boolean[] visited = new boolean[N]; Arrays.fill(visited, false); - + findSubtreeSizes(src, visited, subtreeSizes); + int treeSize = Arrays.stream(subtreeSizes).max().getAsInt(); + + centroidMarked[src] = true; - boolean isCentroid = true; for (int node : tree[src]){ - isCentroid = (subtreeSizes[node] <= (subtreeSizes[src]/2)) ? true : false; + if (subtreeSizes[node] > (treeSize/2)){ + centroidMarked[src] = false; + break; + } } - - if (isCentroid){ + + if (centroidMarked[src]){ + if (src != startingNode && src != previousCentroid) addEdgeCTree(previousCentroid, src); + // centroidTree[previousCentroid].add(src); for (int node : tree[src]){ - + if (!centroidMarked[node]) + findCentroid(node, src); } } else{ - // pick one of the children of the node for next check + // pick one of the children of the root for next check + int nextLargestSubtree = tree[src].getFirst(); + for (int node : tree[src]){ + if (subtreeSizes[node] >= subtreeSizes[nextLargestSubtree]) + nextLargestSubtree = node; + } + findCentroid(nextLargestSubtree, previousCentroid); } } public static void main(String[] args) { - CentroidDecomposition cd = new CentroidDecomposition(16); + CentroidDecomposition cd = new CentroidDecomposition(16, -1); cd.addEdge(0, 1); cd.addEdge(0, 2); cd.addEdge(0, 3); @@ -81,7 +125,22 @@ public static void main(String[] args) { boolean[] visited = new boolean[16]; int[] subtreeSizes = new int[16]; - cd.findSubtreeSizes(3, visited, subtreeSizes); + // int start = cd.startingNode; + int src = (int) ((Math.random() * (15)) + 0); + System.out.println("src= " + src); + + cd.findCentroid(src, src); + + // System.out.println((int)(Math.random() * 16)); + + for (int i = 0; i < cd.centroidTree.length; i++) { + System.out.println(String.format("%s %s", i, cd.centroidTree[i])); + } + + // cd.findSubtreeSizes(8, visited, subtreeSizes); + // for (int i = 0; i < subtreeSizes.length; i++) { + // System.out.println(String.format("%s %s", i, subtreeSizes[i])); + // } } } diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index 2f74d4bc0943..a4f19a81ab76 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -1,16 +1,21 @@ package com.thealgorithms.tree; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Answers.values; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; class CentroidDecompositionTest { private CentroidDecomposition cd; + private int n = 16; @BeforeEach void setUp(){ @@ -45,20 +50,45 @@ void setUp(){ 14 \ 15 + + + + 0 + / | \ + 1 11 8 + / \ / | \ / | \ + 4 5 2 12 14 3 9 10 + / \ / \ + 7 6 15 13 */ } @Test void testFindSubtreeSizes(){ - boolean[] visited = new boolean[16]; - int[] subtreeSizes = new int[16]; + boolean[] visited = new boolean[n]; + int[] subtreeSizes = new int[n]; cd.findSubtreeSizes(3, visited, subtreeSizes); assertEquals(subtreeSizes[8], 3); assertEquals(subtreeSizes[0], 12); } + @RepeatedTest(100) + void testBuildCentroidTree(){ + int src = (int) ((Math.random() * (15)) + 0); + cd.findCentroid(src, src); + List[] centroidTree = cd.getCentroidTree(); + + List correct = new ArrayList(); + correct.add(0); + correct.add(3); + correct.add(9); + correct.add(10); + for (int j = 0; j < centroidTree[8].size(); j++) { + assertEquals(correct.get(j), centroidTree[8].get(j)); + } + } } From b87bcc0b75cc660972d383d4f3f5650be1352d8c Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 12 Nov 2025 18:09:44 +0100 Subject: [PATCH 03/18] throw exception for invalid startNode, add tests, clean up --- .../tree/CentroidDecomposition.java | 32 ++++++++----------- .../tree/CentroidDecompositionTest.java | 22 +++++++------ 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 077f0f677694..9471442d1b9f 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -12,31 +12,28 @@ public class CentroidDecomposition { private int startingNode; private int N; + @SuppressWarnings("unchecked") public CentroidDecomposition(int n, int startingNode){ tree = new ArrayList[n]; centroidTree = new ArrayList[n]; N = n; - for (int i = 0; i < centroidTree.length; i++) centroidTree[i] = new ArrayList<>(); - for (int i = 0; i < tree.length; i++) tree[i] = new ArrayList<>(); centroidMarked = new boolean[n]; centroidParent = new int[n]; - startingNode = (int)(Math.random() * n+1); + if (startingNode < 0 || startingNode > n-1){ + throw new IllegalArgumentException("Starting node must be in range 0.." + (n - 1) + " but got " + startingNode); + } for(int i = 0; i(); + centroidTree[i] = new ArrayList<>(); } } + public CentroidDecomposition(int n){ - tree = new ArrayList[n]; - centroidTree = new ArrayList[n]; - centroidMarked = new boolean[n]; - centroidParent = new int[n]; - startingNode = (this.startingNode == -1) ? (int)(Math.random() * n+1) : this.startingNode; - N = n; - for (int i = 0; i < centroidTree.length; i++) centroidTree[i] = new ArrayList<>(); - for (int i = 0; i < tree.length; i++) tree[i] = new ArrayList<>(); - for(int i = 0; i(); - } + this(n, (int)(Math.random() * n)); + } + + public int getStartingNode(){ + return startingNode; } public List[] getCentroidTree(){ @@ -54,7 +51,6 @@ private void addEdgeCTree(int u, int v){ centroidParent[v] = u; } - public void findSubtreeSizes(int src, boolean[] visited, int[] subtreeSizes){ // dfs traversal to find size of subtree rooted at src visited[src] = true; @@ -87,14 +83,12 @@ public void findCentroid(int src, int previousCentroid){ if (centroidMarked[src]){ if (src != startingNode && src != previousCentroid) addEdgeCTree(previousCentroid, src); - // centroidTree[previousCentroid].add(src); for (int node : tree[src]){ if (!centroidMarked[node]) findCentroid(node, src); } } else{ - // pick one of the children of the root for next check int nextLargestSubtree = tree[src].getFirst(); for (int node : tree[src]){ if (subtreeSizes[node] >= subtreeSizes[nextLargestSubtree]) @@ -105,7 +99,7 @@ public void findCentroid(int src, int previousCentroid){ } public static void main(String[] args) { - CentroidDecomposition cd = new CentroidDecomposition(16, -1); + CentroidDecomposition cd = new CentroidDecomposition(16); cd.addEdge(0, 1); cd.addEdge(0, 2); cd.addEdge(0, 3); @@ -126,7 +120,7 @@ public static void main(String[] args) { int[] subtreeSizes = new int[16]; // int start = cd.startingNode; - int src = (int) ((Math.random() * (15)) + 0); + int src = (int)(Math.random() * 15); System.out.println("src= " + src); cd.findCentroid(src, src); diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index a4f19a81ab76..cec05513b835 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -1,12 +1,9 @@ package com.thealgorithms.tree; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Answers.values; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -37,7 +34,8 @@ void setUp(){ cd.addEdge(14, 15); /* - * 0 + * Initial Tree: + * 0 / | \ 1 2 3 / \ / \ \ @@ -52,7 +50,7 @@ void setUp(){ 15 - + * centroid Tree: 0 / | \ 1 11 8 @@ -74,12 +72,18 @@ void testFindSubtreeSizes(){ assertEquals(subtreeSizes[0], 12); } + @Test + void IllegalArgumentThrows(){ + assertThrows(IllegalArgumentException.class, () -> { + new CentroidDecomposition(10, 99); + }); + } + @RepeatedTest(100) void testBuildCentroidTree(){ - int src = (int) ((Math.random() * (15)) + 0); - cd.findCentroid(src, src); + int start = cd.getStartingNode(); + System.out.println(start); List[] centroidTree = cd.getCentroidTree(); - List correct = new ArrayList(); correct.add(0); correct.add(3); From 95933c2ec74b551f273ae4e08a5c577b82969c7a Mon Sep 17 00:00:00 2001 From: lennart Date: Thu, 13 Nov 2025 11:05:27 +0100 Subject: [PATCH 04/18] make visited and subtreeSizes arrays fields for reuse, test --- .../thealgorithms/tree/CentroidDecomposition.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 9471442d1b9f..9f471968bf76 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -7,6 +7,8 @@ public class CentroidDecomposition { private List[] tree; private List[] centroidTree; + private int[] subtreeSizes; + private boolean[] visited; private boolean[] centroidMarked; private int[] centroidParent; private int startingNode; @@ -19,6 +21,8 @@ public CentroidDecomposition(int n, int startingNode){ N = n; centroidMarked = new boolean[n]; centroidParent = new int[n]; + subtreeSizes = new int[N]; + visited = new boolean[N]; if (startingNode < 0 || startingNode > n-1){ throw new IllegalArgumentException("Starting node must be in range 0.." + (n - 1) + " but got " + startingNode); } @@ -51,6 +55,10 @@ private void addEdgeCTree(int u, int v){ centroidParent[v] = u; } + public int getParent(int v){ + return centroidParent[v]; + } + public void findSubtreeSizes(int src, boolean[] visited, int[] subtreeSizes){ // dfs traversal to find size of subtree rooted at src visited[src] = true; @@ -65,8 +73,8 @@ public void findSubtreeSizes(int src, boolean[] visited, int[] subtreeSizes){ } public void findCentroid(int src, int previousCentroid){ - int[] subtreeSizes = new int[N]; - boolean[] visited = new boolean[N]; + // int[] subtreeSizes = new int[N]; + // boolean[] visited = new boolean[N]; Arrays.fill(visited, false); findSubtreeSizes(src, visited, subtreeSizes); From e9bba9bc86d9a324d20920287d9600c1be76d6ff Mon Sep 17 00:00:00 2001 From: lennart Date: Thu, 13 Nov 2025 12:05:38 +0100 Subject: [PATCH 05/18] clean up find Centroid recursion logic --- .../tree/CentroidDecomposition.java | 48 ++++++++++--------- .../tree/CentroidDecompositionTest.java | 6 +-- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 9f471968bf76..eadc4c6a5e62 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -40,6 +40,10 @@ public int getStartingNode(){ return startingNode; } + public int[] getSubtreeSizes(){ + return subtreeSizes; + } + public List[] getCentroidTree(){ return centroidTree; } @@ -59,52 +63,50 @@ public int getParent(int v){ return centroidParent[v]; } - public void findSubtreeSizes(int src, boolean[] visited, int[] subtreeSizes){ + public void findSubtreeSizes(int src){ // dfs traversal to find size of subtree rooted at src visited[src] = true; subtreeSizes[src] = 1; for (int node : tree[src]){ if(!visited[node] && !centroidMarked[node]){ visited[node] = true; - findSubtreeSizes(node, visited, subtreeSizes); // recurse down to last child node + findSubtreeSizes(node); // recurse down to last child node subtreeSizes[src] += subtreeSizes[node]; // add size of full recursive path to subtree Size of src } } } public void findCentroid(int src, int previousCentroid){ - // int[] subtreeSizes = new int[N]; - // boolean[] visited = new boolean[N]; + Arrays.fill(visited, false); - findSubtreeSizes(src, visited, subtreeSizes); - int treeSize = Arrays.stream(subtreeSizes).max().getAsInt(); + findSubtreeSizes(src); + int treeSize = subtreeSizes[src]; - centroidMarked[src] = true; + int heavyChild = -1; for (int node : tree[src]){ - if (subtreeSizes[node] > (treeSize/2)){ - centroidMarked[src] = false; + if(centroidMarked[node]) continue; + if(subtreeSizes[node] > (treeSize/2)){ + heavyChild = node; break; } } - - if (centroidMarked[src]){ - if (src != startingNode && src != previousCentroid) addEdgeCTree(previousCentroid, src); - for (int node : tree[src]){ - if (!centroidMarked[node]) - findCentroid(node, src); - } + + if (heavyChild != -1){ + findCentroid(heavyChild, previousCentroid); + return; } - else{ - int nextLargestSubtree = tree[src].getFirst(); - for (int node : tree[src]){ - if (subtreeSizes[node] >= subtreeSizes[nextLargestSubtree]) - nextLargestSubtree = node; + + centroidMarked[src] = true; + + if(src != startingNode && src != previousCentroid) addEdge(previousCentroid, src); + + for (int node : tree[src]){ + if (!centroidMarked[node]) + findCentroid(node, src); } - findCentroid(nextLargestSubtree, previousCentroid); } - } public static void main(String[] args) { CentroidDecomposition cd = new CentroidDecomposition(16); diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index cec05513b835..0815819fd0e1 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -64,10 +64,10 @@ void setUp(){ @Test void testFindSubtreeSizes(){ - boolean[] visited = new boolean[n]; - int[] subtreeSizes = new int[n]; + // int[] subtreeSizes = new int[n]; - cd.findSubtreeSizes(3, visited, subtreeSizes); + cd.findSubtreeSizes(3); + int[] subtreeSizes = cd.getSubtreeSizes(); assertEquals(subtreeSizes[8], 3); assertEquals(subtreeSizes[0], 12); } From 14946ed997cb5631f719a81020b6ae49314dc936 Mon Sep 17 00:00:00 2001 From: lennart Date: Thu, 13 Nov 2025 13:07:55 +0100 Subject: [PATCH 06/18] add test for getParent method, make build/reset method --- .../tree/CentroidDecomposition.java | 82 +++++++++++-------- .../tree/CentroidDecompositionTest.java | 74 +++++++++++------ 2 files changed, 98 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index eadc4c6a5e62..9da7ef7cf66e 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -3,10 +3,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collector; +import java.util.stream.Collectors; public class CentroidDecomposition { - private List[] tree; - private List[] centroidTree; + private ArrayList[] tree; + private ArrayList[] centroidTree; private int[] subtreeSizes; private boolean[] visited; private boolean[] centroidMarked; @@ -27,6 +29,7 @@ public CentroidDecomposition(int n, int startingNode){ throw new IllegalArgumentException("Starting node must be in range 0.." + (n - 1) + " but got " + startingNode); } for(int i = 0; i(); centroidTree[i] = new ArrayList<>(); } @@ -36,6 +39,20 @@ public CentroidDecomposition(int n){ this(n, (int)(Math.random() * n)); } + public void build(){ + findCentroid(startingNode, startingNode); + } + + public void reset(){ + for(int i = 0; i(); + centroidParent[i] = -1; + subtreeSizes[i] = 0; + centroidMarked[i] = false; + visited[i] = false; + } + } + public int getStartingNode(){ return startingNode; } @@ -44,11 +61,7 @@ public int[] getSubtreeSizes(){ return subtreeSizes; } - public List[] getCentroidTree(){ - return centroidTree; - } - - public void addEdge(int u, int v){ + public void addEdgeTree(int u, int v){ tree[u].add(v); tree[v].add(u); } @@ -59,19 +72,22 @@ private void addEdgeCTree(int u, int v){ centroidParent[v] = u; } + public ArrayList[] getCentroidTree(){ + return centroidTree; + } + public int getParent(int v){ return centroidParent[v]; } public void findSubtreeSizes(int src){ - // dfs traversal to find size of subtree rooted at src visited[src] = true; subtreeSizes[src] = 1; for (int node : tree[src]){ if(!visited[node] && !centroidMarked[node]){ visited[node] = true; - findSubtreeSizes(node); // recurse down to last child node - subtreeSizes[src] += subtreeSizes[node]; // add size of full recursive path to subtree Size of src + findSubtreeSizes(node); + subtreeSizes[src] += subtreeSizes[node]; } } } @@ -100,7 +116,7 @@ public void findCentroid(int src, int previousCentroid){ centroidMarked[src] = true; - if(src != startingNode && src != previousCentroid) addEdge(previousCentroid, src); + if(src != startingNode && src != previousCentroid) addEdgeCTree(previousCentroid, src); for (int node : tree[src]){ if (!centroidMarked[node]) @@ -110,30 +126,30 @@ public void findCentroid(int src, int previousCentroid){ public static void main(String[] args) { CentroidDecomposition cd = new CentroidDecomposition(16); - cd.addEdge(0, 1); - cd.addEdge(0, 2); - cd.addEdge(0, 3); - cd.addEdge(1, 4); - cd.addEdge(1, 5); - cd.addEdge(2, 6); - cd.addEdge(2, 7); - cd.addEdge(3, 8); - cd.addEdge(8, 9); - cd.addEdge(8, 10); - cd.addEdge(6, 11); - cd.addEdge(11, 12); - cd.addEdge(11, 13); - cd.addEdge(13, 14); - cd.addEdge(14, 15); - - boolean[] visited = new boolean[16]; - int[] subtreeSizes = new int[16]; + cd.addEdgeTree(0, 1); + cd.addEdgeTree(0, 2); + cd.addEdgeTree(0, 3); + cd.addEdgeTree(1, 4); + cd.addEdgeTree(1, 5); + cd.addEdgeTree(2, 6); + cd.addEdgeTree(2, 7); + cd.addEdgeTree(3, 8); + cd.addEdgeTree(8, 9); + cd.addEdgeTree(8, 10); + cd.addEdgeTree(6, 11); + cd.addEdgeTree(11, 12); + cd.addEdgeTree(11, 13); + cd.addEdgeTree(13, 14); + cd.addEdgeTree(14, 15); + + // boolean[] visited = new boolean[16]; + // int[] subtreeSizes = new int[16]; // int start = cd.startingNode; - int src = (int)(Math.random() * 15); - System.out.println("src= " + src); - - cd.findCentroid(src, src); + // int src = (int)(Math.random() * 15); + // System.out.println("src= " + src); + int start = cd.getStartingNode(); + cd.findCentroid(start, start); // System.out.println((int)(Math.random() * 16)); diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index 0815819fd0e1..1c2aa3e758f3 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -12,26 +12,26 @@ class CentroidDecompositionTest { private CentroidDecomposition cd; - private int n = 16; @BeforeEach void setUp(){ cd = new CentroidDecomposition(16); - cd.addEdge(0, 1); - cd.addEdge(0, 2); - cd.addEdge(0, 3); - cd.addEdge(1, 4); - cd.addEdge(1, 5); - cd.addEdge(2, 6); - cd.addEdge(2, 7); - cd.addEdge(3, 8); - cd.addEdge(8, 9); - cd.addEdge(8, 10); - cd.addEdge(6, 11); - cd.addEdge(11, 12); - cd.addEdge(11, 13); - cd.addEdge(13, 14); - cd.addEdge(14, 15); + cd.addEdgeTree(0, 1); + cd.addEdgeTree(0, 2); + cd.addEdgeTree(0, 3); + cd.addEdgeTree(1, 4); + cd.addEdgeTree(1, 5); + cd.addEdgeTree(2, 6); + cd.addEdgeTree(2, 7); + cd.addEdgeTree(3, 8); + cd.addEdgeTree(8, 9); + cd.addEdgeTree(8, 10); + cd.addEdgeTree(6, 11); + cd.addEdgeTree(11, 12); + cd.addEdgeTree(11, 13); + cd.addEdgeTree(13, 14); + cd.addEdgeTree(14, 15); + /* * Initial Tree: @@ -79,19 +79,43 @@ void IllegalArgumentThrows(){ }); } + @Test + void testGetParent(){ + cd.build(); + int three = 8; + int Twelve = 11; + int Five = 1; + int Eleven = 0; + + assertEquals(cd.getParent(3), three); + assertEquals(cd.getParent(12), Twelve); + assertEquals(cd.getParent(5), Five); + assertEquals(cd.getParent(11), Eleven); + + } + @RepeatedTest(100) void testBuildCentroidTree(){ - int start = cd.getStartingNode(); - System.out.println(start); - List[] centroidTree = cd.getCentroidTree(); - List correct = new ArrayList(); - correct.add(0); - correct.add(3); - correct.add(9); - correct.add(10); + cd.build(); + ArrayList[] centroidTree = cd.getCentroidTree(); + ArrayList correctEight = new ArrayList(); + ArrayList correctEleven = new ArrayList(); + correctEight.add(0); + correctEight.add(3); + correctEight.add(9); + correctEight.add(10); + + correctEleven.add(0); + correctEleven.add(2); + correctEleven.add(12); + correctEleven.add(14); + + for (int j = 0; j < centroidTree[8].size(); j++) { + assertEquals(correctEight.get(j), centroidTree[8].get(j)); + } for (int j = 0; j < centroidTree[8].size(); j++) { - assertEquals(correct.get(j), centroidTree[8].get(j)); + assertEquals(correctEleven.get(j), centroidTree[11].get(j)); } } From ebcc878646f677c0f119ff6f21e87b3f18df5063 Mon Sep 17 00:00:00 2001 From: lennart Date: Thu, 13 Nov 2025 13:38:21 +0100 Subject: [PATCH 07/18] add full test coverage for current version --- .../tree/CentroidDecomposition.java | 6 + .../tree/CentroidDecompositionTest.java | 143 ++++++++++++++++-- 2 files changed, 136 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 9da7ef7cf66e..fe3c82af5065 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -80,6 +80,12 @@ public int getParent(int v){ return centroidParent[v]; } + public List getCentroidChildren(int v) { + return centroidTree[v].stream() + .filter(child -> centroidParent[child] == v && centroidParent[v] != child) + .collect(Collectors.toList()); + } + public void findSubtreeSizes(int src){ visited[src] = true; subtreeSizes[src] = 1; diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index 1c2aa3e758f3..e75577536ada 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -2,9 +2,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.RepeatedTest; @@ -62,6 +66,27 @@ void setUp(){ */ } + @Test + void startingNodeIsInRangeWhenRandomCtorUsed() { + int n = 32; + CentroidDecomposition rnd = new CentroidDecomposition(n); + int s = rnd.getStartingNode(); + assertTrue(0 <= s && s < n, "starting node must be in [0, n)"); + } + + @Test + void invalidStartingNodeThrows() { + assertThrows(IllegalArgumentException.class, () -> new CentroidDecomposition(10, -1)); + assertThrows(IllegalArgumentException.class, () -> new CentroidDecomposition(10, 10)); + } + + @Test + void IllegalArgumentThrows(){ + assertThrows(IllegalArgumentException.class, () -> { + new CentroidDecomposition(10, 99); + }); + } + @Test void testFindSubtreeSizes(){ // int[] subtreeSizes = new int[n]; @@ -73,25 +98,117 @@ void testFindSubtreeSizes(){ } @Test - void IllegalArgumentThrows(){ - assertThrows(IllegalArgumentException.class, () -> { - new CentroidDecomposition(10, 99); - }); + void getCentroidChildrenReturnsDirectedChildrenOnly() { + cd.build(); + + // children of 0 in centroid tree should be [1, 11, 8] in any order + List children0 = cd.getCentroidChildren(0).stream().sorted().collect(Collectors.toList()); + assertEquals(Arrays.asList(1, 8, 11), children0); + + // children of 11 should be [2, 12, 14] + List children11 = cd.getCentroidChildren(11).stream().sorted().collect(Collectors.toList()); + assertEquals(Arrays.asList(2, 12, 14), children11); + + // leaves have no children + assertTrue(cd.getCentroidChildren(4).isEmpty()); + assertTrue(cd.getCentroidChildren(9).isEmpty()); + assertTrue(cd.getCentroidChildren(15).isEmpty()); + } + @Test + void buildSetsExpectedParentsForKeyNodes() { + cd.build(); + // parent(11) = 0, parent(1) = 0, parent(8) = 0 + assertEquals(0, cd.getParent(11)); + assertEquals(0, cd.getParent(1)); + assertEquals(0, cd.getParent(8)); + + assertEquals(1, cd.getParent(4)); + assertEquals(1, cd.getParent(5)); + + assertEquals(11, cd.getParent(2)); + assertEquals(11, cd.getParent(12)); + assertEquals(11, cd.getParent(14)); + + assertEquals(2, cd.getParent(6)); + assertEquals(2, cd.getParent(7)); + + assertEquals(14, cd.getParent(13)); + assertEquals(14, cd.getParent(15)); + + assertEquals(8, cd.getParent(3)); + assertEquals(8, cd.getParent(9)); + assertEquals(8, cd.getParent(10)); + } + + @Test + void centroidAdjacencyFor8And11MatchesIgnoringOrder() { + cd.build(); + ArrayList[] cg = cd.getCentroidTree(); + + List actual8 = new ArrayList<>(cg[8]); + List actual11 = new ArrayList<>(cg[11]); + + List expected8 = Arrays.asList(0, 3, 9, 10); + List expected11 = Arrays.asList(0, 2, 12, 14); + + Collections.sort(actual8); + Collections.sort(actual11); + List e8 = expected8.stream().sorted().collect(Collectors.toList()); + List e11 = expected11.stream().sorted().collect(Collectors.toList()); + + assertEquals(e8, actual8); + assertEquals(e11, actual11); } @Test - void testGetParent(){ + void resetClearsCentroidData() { cd.build(); - int three = 8; - int Twelve = 11; - int Five = 1; - int Eleven = 0; + cd.reset(); - assertEquals(cd.getParent(3), three); - assertEquals(cd.getParent(12), Twelve); - assertEquals(cd.getParent(5), Five); - assertEquals(cd.getParent(11), Eleven); + ArrayList[] cg = cd.getCentroidTree(); + for (int i = 0; i < cg.length; i++) { + assertTrue(cg[i].isEmpty(), "centroid adjacency must be empty after reset"); + assertEquals(-1, cd.getParent(i), "parent must be -1 after reset"); + } + + cd.build(); + assertEquals(0, cd.getParent(11)); + } + + @Test + void buildingFromDifferentStartNodesProducesSameParents() { + // build from 0 + CentroidDecomposition a = new CentroidDecomposition(16, 0); + copyEdges(cd, a); + a.build(); + + // build from 7 + CentroidDecomposition b = new CentroidDecomposition(16, 7); + copyEdges(cd, b); + b.build(); + + // compare parent arrays node by node + for (int v = 0; v < 16; v++) { + assertEquals(a.getParent(v), b.getParent(v), "parent mismatch at node " + v); + } + } + private static void copyEdges(CentroidDecomposition from, CentroidDecomposition to) { + to.addEdgeTree(0, 1); + to.addEdgeTree(0, 2); + to.addEdgeTree(0, 3); + to.addEdgeTree(1, 4); + to.addEdgeTree(1, 5); + to.addEdgeTree(2, 6); + to.addEdgeTree(2, 7); + to.addEdgeTree(3, 8); + to.addEdgeTree(8, 9); + to.addEdgeTree(8, 10); + to.addEdgeTree(6, 11); + to.addEdgeTree(11, 12); + to.addEdgeTree(11, 13); + to.addEdgeTree(13, 14); + to.addEdgeTree(14, 15); } @RepeatedTest(100) From 57be79f84c1fe08d7af1d86474d9c9abb90f8bd1 Mon Sep 17 00:00:00 2001 From: lennart Date: Mon, 17 Nov 2025 12:58:11 +0100 Subject: [PATCH 08/18] add getRoot, add forEachAncestor, edit Tests --- .../tree/CentroidDecomposition.java | 75 ++++++++++--------- .../tree/CentroidDecompositionTest.java | 38 +++++++--- 2 files changed, 64 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index fe3c82af5065..b8360458a082 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -3,9 +3,26 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.stream.Collector; +import java.util.function.IntConsumer; import java.util.stream.Collectors; +/** + * CentroidDecomposition builds the centroid decomposition of an undirected tree. + * + *

The algorithm recursively finds centroids, marks them as removed, and + * constructs a centroid tree whose height is O(log n). This structure enables + * efficient solutions for distance queries, nearest-colored-node queries, and + * other divide-and-conquer algorithms on trees.

+ * + *

Supports adding edges of the original tree, building the centroid tree, + * and querying centroid parents, children, and internal state. + *

+ * + *

Author: Lennart S.
+ * GitHub: https://github.com/lens161
+ *

+ */ + public class CentroidDecomposition { private ArrayList[] tree; private ArrayList[] centroidTree; @@ -28,6 +45,7 @@ public CentroidDecomposition(int n, int startingNode){ if (startingNode < 0 || startingNode > n-1){ throw new IllegalArgumentException("Starting node must be in range 0.." + (n - 1) + " but got " + startingNode); } + this.startingNode = startingNode; for(int i = 0; i(); @@ -40,6 +58,7 @@ public CentroidDecomposition(int n){ } public void build(){ + reset(); findCentroid(startingNode, startingNode); } @@ -86,6 +105,15 @@ public List getCentroidChildren(int v) { .collect(Collectors.toList()); } + public int getRoot() { + for (int i = 0; i < N; i++) { + if (centroidParent[i] == -1) { + return i; + } + } + throw new IllegalStateException("Centroid tree has no root. likely it was not built"); + } + public void findSubtreeSizes(int src){ visited[src] = true; subtreeSizes[src] = 1; @@ -130,43 +158,16 @@ public void findCentroid(int src, int previousCentroid){ } } - public static void main(String[] args) { - CentroidDecomposition cd = new CentroidDecomposition(16); - cd.addEdgeTree(0, 1); - cd.addEdgeTree(0, 2); - cd.addEdgeTree(0, 3); - cd.addEdgeTree(1, 4); - cd.addEdgeTree(1, 5); - cd.addEdgeTree(2, 6); - cd.addEdgeTree(2, 7); - cd.addEdgeTree(3, 8); - cd.addEdgeTree(8, 9); - cd.addEdgeTree(8, 10); - cd.addEdgeTree(6, 11); - cd.addEdgeTree(11, 12); - cd.addEdgeTree(11, 13); - cd.addEdgeTree(13, 14); - cd.addEdgeTree(14, 15); - - // boolean[] visited = new boolean[16]; - // int[] subtreeSizes = new int[16]; - - // int start = cd.startingNode; - // int src = (int)(Math.random() * 15); - // System.out.println("src= " + src); - int start = cd.getStartingNode(); - cd.findCentroid(start, start); - - // System.out.println((int)(Math.random() * 16)); - - for (int i = 0; i < cd.centroidTree.length; i++) { - System.out.println(String.format("%s %s", i, cd.centroidTree[i])); + /** + * Applies the given action to all centroid ancestors of the given node, + * including the node itself, walking up via centroidParent[] until the root. + */ + public void forEachAcestor(int centroid, IntConsumer action){ + int curr = centroid; + while(curr != -1){ + action.accept(curr); + curr = getParent(curr); } - - // cd.findSubtreeSizes(8, visited, subtreeSizes); - // for (int i = 0; i < subtreeSizes.length; i++) { - // System.out.println(String.format("%s %s", i, subtreeSizes[i])); - // } } } diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index e75577536ada..29498104a509 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -11,7 +11,6 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; class CentroidDecompositionTest { @@ -19,7 +18,7 @@ class CentroidDecompositionTest { @BeforeEach void setUp(){ - cd = new CentroidDecomposition(16); + cd = new CentroidDecomposition(16, 0); cd.addEdgeTree(0, 1); cd.addEdgeTree(0, 2); cd.addEdgeTree(0, 3); @@ -54,7 +53,7 @@ void setUp(){ 15 - * centroid Tree: + * centroid Tree (starting at 0): 0 / | \ 1 11 8 @@ -176,23 +175,38 @@ void resetClearsCentroidData() { } @Test - void buildingFromDifferentStartNodesProducesSameParents() { - // build from 0 + void buildingFromDifferentStartNodesYieldsValidTrees() { CentroidDecomposition a = new CentroidDecomposition(16, 0); copyEdges(cd, a); a.build(); + assertValidCentroidTree(a, 16); - // build from 7 CentroidDecomposition b = new CentroidDecomposition(16, 7); copyEdges(cd, b); b.build(); + assertValidCentroidTree(b, 16); + } + + private static void assertValidCentroidTree(CentroidDecomposition cd, int n) { + int roots = 0; + int edges = 0; - // compare parent arrays node by node - for (int v = 0; v < 16; v++) { - assertEquals(a.getParent(v), b.getParent(v), "parent mismatch at node " + v); + ArrayList[] cg = cd.getCentroidTree(); + + for (int v = 0; v < n; v++) { + if (cd.getParent(v) == -1) { + roots++; + } + edges += cg[v].size(); } - } + // undirected edges counted twice + edges /= 2; + + assertEquals(1, roots, "must have exactly one root"); + assertEquals(n - 1, edges, "centroid tree must have n-1 edges"); + } + private static void copyEdges(CentroidDecomposition from, CentroidDecomposition to) { to.addEdgeTree(0, 1); to.addEdgeTree(0, 2); @@ -211,7 +225,7 @@ private static void copyEdges(CentroidDecomposition from, CentroidDecomposition to.addEdgeTree(14, 15); } - @RepeatedTest(100) + @Test void testBuildCentroidTree(){ cd.build(); ArrayList[] centroidTree = cd.getCentroidTree(); @@ -231,7 +245,7 @@ void testBuildCentroidTree(){ assertEquals(correctEight.get(j), centroidTree[8].get(j)); } - for (int j = 0; j < centroidTree[8].size(); j++) { + for (int j = 0; j < centroidTree[11].size(); j++) { assertEquals(correctEleven.get(j), centroidTree[11].get(j)); } From 3045bda108692adeb501c088d3075af72df07c6e Mon Sep 17 00:00:00 2001 From: lennart Date: Mon, 17 Nov 2025 13:10:25 +0100 Subject: [PATCH 09/18] add Test for forEachAncestor --- .../tree/CentroidDecomposition.java | 2 +- .../tree/CentroidDecompositionTest.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index b8360458a082..2b7cf42a30c7 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -162,7 +162,7 @@ public void findCentroid(int src, int previousCentroid){ * Applies the given action to all centroid ancestors of the given node, * including the node itself, walking up via centroidParent[] until the root. */ - public void forEachAcestor(int centroid, IntConsumer action){ + public void forEachAncestor(int centroid, IntConsumer action){ int curr = centroid; while(curr != -1){ action.accept(curr); diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index 29498104a509..257fad1d161e 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -174,6 +174,30 @@ void resetClearsCentroidData() { assertEquals(0, cd.getParent(11)); } + + @Test + void forEachAncestorVisitsCorrectChain() { + cd.build(); + + int[] testNodes = {0, 3, 7, 11, 15}; + + for (int node : testNodes) { + + List visited = new ArrayList<>(); + cd.forEachAncestor(node, visited::add); + + List expected = new ArrayList<>(); + int curr = node; + while (curr != -1) { + expected.add(curr); + curr = cd.getParent(curr); + } + + assertEquals(expected, visited, + "forEachAncestor wrong for node " + node); + } + } + @Test void buildingFromDifferentStartNodesYieldsValidTrees() { CentroidDecomposition a = new CentroidDecomposition(16, 0); From d21a84c6765800e7b95a75e10438beae287841b1 Mon Sep 17 00:00:00 2001 From: lennart Date: Mon, 17 Nov 2025 14:34:08 +0100 Subject: [PATCH 10/18] refactor to fit code style requirements, run CI check --- .../tree/CentroidDecomposition.java | 36 ++++++++++--------- .../tree/CentroidDecompositionTest.java | 12 +++---- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 2b7cf42a30c7..4924141ac427 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -7,16 +7,18 @@ import java.util.stream.Collectors; /** - * CentroidDecomposition builds the centroid decomposition of an undirected tree. + * Implements centroid decomposition on a tree. * - *

The algorithm recursively finds centroids, marks them as removed, and - * constructs a centroid tree whose height is O(log n). This structure enables - * efficient solutions for distance queries, nearest-colored-node queries, and - * other divide-and-conquer algorithms on trees.

+ * A centroid of a tree is a node whose removal splits the tree into + * components of size at most n/2. Centroid decomposition recursively + * finds centroids and builds a centroid tree of height O(log n). * - *

Supports adding edges of the original tree, building the centroid tree, - * and querying centroid parents, children, and internal state. - *

+ * This class supports: + *
    + *
  • Adding edges to the original tree
  • + *
  • Building the centroid decomposition
  • + *
  • Querying parents and children in the centroid tree
  • + *
* *

Author: Lennart S.
* GitHub: https://github.com/lens161
@@ -24,19 +26,19 @@ */ public class CentroidDecomposition { - private ArrayList[] tree; - private ArrayList[] centroidTree; + private final List[] tree; + private final List[] centroidTree; private int[] subtreeSizes; private boolean[] visited; private boolean[] centroidMarked; private int[] centroidParent; private int startingNode; - private int N; + private final int N; @SuppressWarnings("unchecked") public CentroidDecomposition(int n, int startingNode){ - tree = new ArrayList[n]; - centroidTree = new ArrayList[n]; + this.tree = (List[]) new ArrayList[n]; + this.centroidTree = (List[]) new ArrayList[n]; N = n; centroidMarked = new boolean[n]; centroidParent = new int[n]; @@ -64,7 +66,7 @@ public void build(){ public void reset(){ for(int i = 0; i(); + centroidTree[i].clear(); centroidParent[i] = -1; subtreeSizes[i] = 0; centroidMarked[i] = false; @@ -91,7 +93,7 @@ private void addEdgeCTree(int u, int v){ centroidParent[v] = u; } - public ArrayList[] getCentroidTree(){ + public List[] getCentroidTree(){ return centroidTree; } @@ -150,7 +152,9 @@ public void findCentroid(int src, int previousCentroid){ centroidMarked[src] = true; - if(src != startingNode && src != previousCentroid) addEdgeCTree(previousCentroid, src); + if(src != startingNode && src != previousCentroid){ + addEdgeCTree(previousCentroid, src); + } for (int node : tree[src]){ if (!centroidMarked[node]) diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index 257fad1d161e..d446ccd2f3ba 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -142,7 +142,7 @@ void buildSetsExpectedParentsForKeyNodes() { @Test void centroidAdjacencyFor8And11MatchesIgnoringOrder() { cd.build(); - ArrayList[] cg = cd.getCentroidTree(); + List[] cg = cd.getCentroidTree(); List actual8 = new ArrayList<>(cg[8]); List actual11 = new ArrayList<>(cg[11]); @@ -164,7 +164,7 @@ void resetClearsCentroidData() { cd.build(); cd.reset(); - ArrayList[] cg = cd.getCentroidTree(); + List[] cg = cd.getCentroidTree(); for (int i = 0; i < cg.length; i++) { assertTrue(cg[i].isEmpty(), "centroid adjacency must be empty after reset"); assertEquals(-1, cd.getParent(i), "parent must be -1 after reset"); @@ -215,7 +215,7 @@ private static void assertValidCentroidTree(CentroidDecomposition cd, int n) { int roots = 0; int edges = 0; - ArrayList[] cg = cd.getCentroidTree(); + List[] cg = cd.getCentroidTree(); for (int v = 0; v < n; v++) { if (cd.getParent(v) == -1) { @@ -252,9 +252,9 @@ private static void copyEdges(CentroidDecomposition from, CentroidDecomposition @Test void testBuildCentroidTree(){ cd.build(); - ArrayList[] centroidTree = cd.getCentroidTree(); - ArrayList correctEight = new ArrayList(); - ArrayList correctEleven = new ArrayList(); + List[] centroidTree = cd.getCentroidTree(); + List correctEight = new ArrayList(); + List correctEleven = new ArrayList(); correctEight.add(0); correctEight.add(3); correctEight.add(9); From 13dcc8954acdef7c590dccf4db07559ce40cd8ee Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 19 Nov 2025 18:04:51 +0100 Subject: [PATCH 11/18] fix formatting issues --- .../tree/CentroidDecomposition.java | 39 ++++++++++--------- .../tree/CentroidDecompositionTest.java | 2 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 4924141ac427..1b27ce8aa11f 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -20,8 +20,10 @@ *

  • Querying parents and children in the centroid tree
  • * * - *

    Author: Lennart S.
    - * GitHub: https://github.com/lens161
    + *

    + * Reference: https://medium.com/carpanese/an-illustrated-introduction-to-centroid-decomposition-8c1989d53308
    + * Author: Lennart S.
    + * GitHub: https://github.com/lens161
    *

    */ @@ -55,7 +57,7 @@ public CentroidDecomposition(int n, int startingNode){ } } - public CentroidDecomposition(int n){ + public CentroidDecomposition(int n) { this(n, (int)(Math.random() * n)); } @@ -64,7 +66,7 @@ public void build(){ findCentroid(startingNode, startingNode); } - public void reset(){ + public void reset() { for(int i = 0; i[] getCentroidTree(){ + public List[] getCentroidTree() { return centroidTree; } - public int getParent(int v){ + public int getParent(int v) { return centroidParent[v]; } public List getCentroidChildren(int v) { return centroidTree[v].stream() - .filter(child -> centroidParent[child] == v && centroidParent[v] != child) - .collect(Collectors.toList()); + .filter(child -> centroidParent[child] == v && centroidParent[v] != child).collect(Collectors.toList()); } public int getRoot() { @@ -116,7 +117,7 @@ public int getRoot() { throw new IllegalStateException("Centroid tree has no root. likely it was not built"); } - public void findSubtreeSizes(int src){ + public void findSubtreeSizes(int src) { visited[src] = true; subtreeSizes[src] = 1; for (int node : tree[src]){ @@ -128,7 +129,7 @@ public void findSubtreeSizes(int src){ } } - public void findCentroid(int src, int previousCentroid){ + public void findCentroid(int src, int previousCentroid) { Arrays.fill(visited, false); @@ -137,7 +138,7 @@ public void findCentroid(int src, int previousCentroid){ int heavyChild = -1; - for (int node : tree[src]){ + for (int node : tree[src]) { if(centroidMarked[node]) continue; if(subtreeSizes[node] > (treeSize/2)){ heavyChild = node; @@ -145,18 +146,18 @@ public void findCentroid(int src, int previousCentroid){ } } - if (heavyChild != -1){ + if (heavyChild != -1) { findCentroid(heavyChild, previousCentroid); return; } centroidMarked[src] = true; - if(src != startingNode && src != previousCentroid){ + if(src != startingNode && src != previousCentroid) { addEdgeCTree(previousCentroid, src); } - for (int node : tree[src]){ + for (int node : tree[src]) { if (!centroidMarked[node]) findCentroid(node, src); } @@ -166,7 +167,7 @@ public void findCentroid(int src, int previousCentroid){ * Applies the given action to all centroid ancestors of the given node, * including the node itself, walking up via centroidParent[] until the root. */ - public void forEachAncestor(int centroid, IntConsumer action){ + public void forEachAncestor(int centroid, IntConsumer action) { int curr = centroid; while(curr != -1){ action.accept(curr); diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index d446ccd2f3ba..f666c6d6d2ff 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -250,7 +250,7 @@ private static void copyEdges(CentroidDecomposition from, CentroidDecomposition } @Test - void testBuildCentroidTree(){ + void testBuildCentroidTree() { cd.build(); List[] centroidTree = cd.getCentroidTree(); List correctEight = new ArrayList(); From 8b0d4a31569b08b60c9c6a8625e2980374d1741a Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 19 Nov 2025 18:14:37 +0100 Subject: [PATCH 12/18] fix formatting issues --- .../tree/CentroidDecomposition.java | 32 +++++++++---------- .../tree/CentroidDecompositionTest.java | 15 ++++----- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 1b27ce8aa11f..7bae7a41d681 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -38,7 +38,7 @@ public class CentroidDecomposition { private final int N; @SuppressWarnings("unchecked") - public CentroidDecomposition(int n, int startingNode){ + public CentroidDecomposition(int n, int startingNode) { this.tree = (List[]) new ArrayList[n]; this.centroidTree = (List[]) new ArrayList[n]; N = n; @@ -46,11 +46,11 @@ public CentroidDecomposition(int n, int startingNode){ centroidParent = new int[n]; subtreeSizes = new int[N]; visited = new boolean[N]; - if (startingNode < 0 || startingNode > n-1){ + if (startingNode < 0 || startingNode > n - 1) { throw new IllegalArgumentException("Starting node must be in range 0.." + (n - 1) + " but got " + startingNode); } this.startingNode = startingNode; - for(int i = 0; i(); centroidTree[i] = new ArrayList<>(); @@ -58,16 +58,16 @@ public CentroidDecomposition(int n, int startingNode){ } public CentroidDecomposition(int n) { - this(n, (int)(Math.random() * n)); + this(n, (int) (Math.random() * n)); } - public void build(){ + public void build() { reset(); findCentroid(startingNode, startingNode); } public void reset() { - for(int i = 0; i getCentroidChildren(int v) { - return centroidTree[v].stream() - .filter(child -> centroidParent[child] == v && centroidParent[v] != child).collect(Collectors.toList()); + return centroidTree[v].stream().filter(child -> centroidParent[child] == v && centroidParent[v] != child).collect(Collectors.toList()); } public int getRoot() { @@ -120,8 +119,8 @@ public int getRoot() { public void findSubtreeSizes(int src) { visited[src] = true; subtreeSizes[src] = 1; - for (int node : tree[src]){ - if(!visited[node] && !centroidMarked[node]){ + for (int node : tree[src]) { + if (!visited[node] && !centroidMarked[node]) { visited[node] = true; findSubtreeSizes(node); subtreeSizes[src] += subtreeSizes[node]; @@ -129,7 +128,7 @@ public void findSubtreeSizes(int src) { } } - public void findCentroid(int src, int previousCentroid) { + public void findCentroid(int src, int previousCentroid) { Arrays.fill(visited, false); @@ -139,8 +138,8 @@ public void findCentroid(int src, int previousCentroid) { int heavyChild = -1; for (int node : tree[src]) { - if(centroidMarked[node]) continue; - if(subtreeSizes[node] > (treeSize/2)){ + if (centroidMarked[node]) continue; + if (subtreeSizes[node] > (treeSize/2)) { heavyChild = node; break; } @@ -153,13 +152,12 @@ public void findCentroid(int src, int previousCentroid) { centroidMarked[src] = true; - if(src != startingNode && src != previousCentroid) { + if (src != startingNode && src != previousCentroid) { addEdgeCTree(previousCentroid, src); } for (int node : tree[src]) { - if (!centroidMarked[node]) - findCentroid(node, src); + if (!centroidMarked[node]) findCentroid(node, src); } } @@ -169,7 +167,7 @@ public void findCentroid(int src, int previousCentroid) { */ public void forEachAncestor(int centroid, IntConsumer action) { int curr = centroid; - while(curr != -1){ + while (curr != -1){ action.accept(curr); curr = getParent(curr); } diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index f666c6d6d2ff..df046f692177 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -17,7 +17,7 @@ class CentroidDecompositionTest { private CentroidDecomposition cd; @BeforeEach - void setUp(){ + void setUp() { cd = new CentroidDecomposition(16, 0); cd.addEdgeTree(0, 1); cd.addEdgeTree(0, 2); @@ -65,7 +65,7 @@ void setUp(){ */ } - @Test + @Test void startingNodeIsInRangeWhenRandomCtorUsed() { int n = 32; CentroidDecomposition rnd = new CentroidDecomposition(n); @@ -80,14 +80,12 @@ void invalidStartingNodeThrows() { } @Test - void IllegalArgumentThrows(){ - assertThrows(IllegalArgumentException.class, () -> { - new CentroidDecomposition(10, 99); - }); + void IllegalArgumentThrows() { + assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition(10, 99);}); } @Test - void testFindSubtreeSizes(){ + void testFindSubtreeSizes() { // int[] subtreeSizes = new int[n]; cd.findSubtreeSizes(3); @@ -193,8 +191,7 @@ void forEachAncestorVisitsCorrectChain() { curr = cd.getParent(curr); } - assertEquals(expected, visited, - "forEachAncestor wrong for node " + node); + assertEquals(expected, visited, "forEachAncestor wrong for node " + node); } } From 718f04ac0e1fd53ab2fa3f518cac485a2c72d2eb Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 19 Nov 2025 18:19:51 +0100 Subject: [PATCH 13/18] fix formatting issues --- .../com/thealgorithms/tree/CentroidDecomposition.java | 8 ++++---- .../com/thealgorithms/tree/CentroidDecompositionTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 7bae7a41d681..478504836c2f 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -50,7 +50,7 @@ public CentroidDecomposition(int n, int startingNode) { throw new IllegalArgumentException("Starting node must be in range 0.." + (n - 1) + " but got " + startingNode); } this.startingNode = startingNode; - for (int i = 0; i < n; i++){ + for (int i = 0; i < n; i++) { centroidParent[i] = -1; tree[i] = new ArrayList<>(); centroidTree[i] = new ArrayList<>(); @@ -139,7 +139,7 @@ public void findCentroid(int src, int previousCentroid) { for (int node : tree[src]) { if (centroidMarked[node]) continue; - if (subtreeSizes[node] > (treeSize/2)) { + if (subtreeSizes[node] > (treeSize / 2)) { heavyChild = node; break; } @@ -158,8 +158,8 @@ public void findCentroid(int src, int previousCentroid) { for (int node : tree[src]) { if (!centroidMarked[node]) findCentroid(node, src); - } } + } /** * Applies the given action to all centroid ancestors of the given node, @@ -167,7 +167,7 @@ public void findCentroid(int src, int previousCentroid) { */ public void forEachAncestor(int centroid, IntConsumer action) { int curr = centroid; - while (curr != -1){ + while (curr != -1) { action.accept(curr); curr = getParent(curr); } diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index df046f692177..414ae2326909 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -81,7 +81,7 @@ void invalidStartingNodeThrows() { @Test void IllegalArgumentThrows() { - assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition(10, 99);}); + assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition(10, 99); }); } @Test From 737c0f24034cef4843d767f2a0203f556935c095 Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 19 Nov 2025 18:28:54 +0100 Subject: [PATCH 14/18] fix formatting issues --- .../com/thealgorithms/tree/CentroidDecomposition.java | 11 ++++------- .../thealgorithms/tree/CentroidDecompositionTest.java | 5 ----- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 478504836c2f..acacf5540382 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -127,14 +127,12 @@ public void findSubtreeSizes(int src) { } } } - - public void findCentroid(int src, int previousCentroid) { - Arrays.fill(visited, false); + public void findCentroid(int src, int previousCentroid) { + Arrays.fill(visited, false); findSubtreeSizes(src); int treeSize = subtreeSizes[src]; - int heavyChild = -1; for (int node : tree[src]) { @@ -149,12 +147,12 @@ public void findCentroid(int src, int previousCentroid) { findCentroid(heavyChild, previousCentroid); return; } - + centroidMarked[src] = true; if (src != startingNode && src != previousCentroid) { addEdgeCTree(previousCentroid, src); - } + } for (int node : tree[src]) { if (!centroidMarked[node]) findCentroid(node, src); @@ -172,5 +170,4 @@ public void forEachAncestor(int centroid, IntConsumer action) { curr = getParent(curr); } } - } diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index 414ae2326909..25bd855af9c9 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -9,7 +9,6 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,7 +34,6 @@ void setUp() { cd.addEdgeTree(13, 14); cd.addEdgeTree(14, 15); - /* * Initial Tree: * 0 @@ -52,7 +50,6 @@ void setUp() { \ 15 - * centroid Tree (starting at 0): 0 / | \ @@ -172,7 +169,6 @@ void resetClearsCentroidData() { assertEquals(0, cd.getParent(11)); } - @Test void forEachAncestorVisitsCorrectChain() { cd.build(); @@ -269,6 +265,5 @@ void testBuildCentroidTree() { for (int j = 0; j < centroidTree[11].size(); j++) { assertEquals(correctEleven.get(j), centroidTree[11].get(j)); } - } } From 98e50e7335bba7bdb85aedcf0f541615bcaeb69f Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 19 Nov 2025 18:31:05 +0100 Subject: [PATCH 15/18] fix formatting issues --- src/main/java/com/thealgorithms/tree/CentroidDecomposition.java | 1 - .../java/com/thealgorithms/tree/CentroidDecompositionTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index acacf5540382..11e94deb6760 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -129,7 +129,6 @@ public void findSubtreeSizes(int src) { } public void findCentroid(int src, int previousCentroid) { - Arrays.fill(visited, false); findSubtreeSizes(src); int treeSize = subtreeSizes[src]; diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index 25bd855af9c9..ad51608ab9e0 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -58,7 +58,6 @@ void setUp() { 4 5 2 12 14 3 9 10 / \ / \ 7 6 15 13 - */ } From 135a7c5474a9d88cca12a5761e7d9e3dee0cfebf Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 19 Nov 2025 18:33:26 +0100 Subject: [PATCH 16/18] fix formatting issues --- src/main/java/com/thealgorithms/tree/CentroidDecomposition.java | 2 +- .../java/com/thealgorithms/tree/CentroidDecompositionTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 11e94deb6760..729d604e0ea6 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -154,7 +154,7 @@ public void findCentroid(int src, int previousCentroid) { } for (int node : tree[src]) { - if (!centroidMarked[node]) findCentroid(node, src); + if (!centroidMarked[node]) findCentroid(node, src); } } diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index ad51608ab9e0..26f2f2a4b175 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -222,7 +222,7 @@ private static void assertValidCentroidTree(CentroidDecomposition cd, int n) { assertEquals(1, roots, "must have exactly one root"); assertEquals(n - 1, edges, "centroid tree must have n-1 edges"); } - + private static void copyEdges(CentroidDecomposition from, CentroidDecomposition to) { to.addEdgeTree(0, 1); to.addEdgeTree(0, 2); From 90cae7f6cc224d42972bd176a32fa6b69a612a6a Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 19 Nov 2025 18:41:33 +0100 Subject: [PATCH 17/18] fix formatting issues --- .../java/com/thealgorithms/tree/CentroidDecomposition.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index 729d604e0ea6..a00eec68e5ca 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -21,7 +21,8 @@ * * *

    - * Reference: https://medium.com/carpanese/an-illustrated-introduction-to-centroid-decomposition-8c1989d53308
    + * Reference: + * https://medium.com/carpanese/an-illustrated-introduction-to-centroid-decomposition-8c1989d53308
    * Author: Lennart S.
    * GitHub: https://github.com/lens161
    *

    @@ -154,7 +155,9 @@ public void findCentroid(int src, int previousCentroid) { } for (int node : tree[src]) { - if (!centroidMarked[node]) findCentroid(node, src); + if (!centroidMarked[node]) { + findCentroid(node, src); + } } } From e96043f5c00059bad2b21a5647d20cd529da6e18 Mon Sep 17 00:00:00 2001 From: lennart Date: Wed, 19 Nov 2025 18:58:48 +0100 Subject: [PATCH 18/18] fix style problems --- .../tree/CentroidDecomposition.java | 16 +++++++++------- .../tree/CentroidDecompositionTest.java | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java index a00eec68e5ca..ff3a6a8df5d3 100644 --- a/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/CentroidDecomposition.java @@ -36,17 +36,17 @@ public class CentroidDecomposition { private boolean[] centroidMarked; private int[] centroidParent; private int startingNode; - private final int N; + private final int n; @SuppressWarnings("unchecked") public CentroidDecomposition(int n, int startingNode) { this.tree = (List[]) new ArrayList[n]; this.centroidTree = (List[]) new ArrayList[n]; - N = n; + this.n = n; centroidMarked = new boolean[n]; centroidParent = new int[n]; - subtreeSizes = new int[N]; - visited = new boolean[N]; + subtreeSizes = new int[n]; + visited = new boolean[n]; if (startingNode < 0 || startingNode > n - 1) { throw new IllegalArgumentException("Starting node must be in range 0.." + (n - 1) + " but got " + startingNode); } @@ -68,7 +68,7 @@ public void build() { } public void reset() { - for (int i = 0; i < N; i++) { + for (int i = 0; i < n; i++) { centroidTree[i].clear(); centroidParent[i] = -1; subtreeSizes[i] = 0; @@ -109,7 +109,7 @@ public List getCentroidChildren(int v) { } public int getRoot() { - for (int i = 0; i < N; i++) { + for (int i = 0; i < n; i++) { if (centroidParent[i] == -1) { return i; } @@ -136,7 +136,9 @@ public void findCentroid(int src, int previousCentroid) { int heavyChild = -1; for (int node : tree[src]) { - if (centroidMarked[node]) continue; + if (centroidMarked[node]) { + continue; + } if (subtreeSizes[node] > (treeSize / 2)) { heavyChild = node; break; diff --git a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java index 26f2f2a4b175..05ae772cc827 100644 --- a/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/CentroidDecompositionTest.java @@ -76,7 +76,7 @@ void invalidStartingNodeThrows() { } @Test - void IllegalArgumentThrows() { + void illegalArgumentThrows() { assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition(10, 99); }); }