11/**
2- * Determines if two unrooted trees are isomorphic. This algorithm can easily be modified to support
3- * checking if two rooted trees are isomorphic.
2+ * Tree Isomorphism — Canonical Encoding
43 *
5- * <p>Tested code against: https://uva.onlinejudge.org/external/124/p12489.pdf
4+ * Determines if two unrooted trees are isomorphic (structurally identical
5+ * regardless of labeling). The algorithm works in three steps:
6+ *
7+ * 1. Find the center(s) of each tree by iteratively pruning leaf nodes.
8+ * A tree has 1 or 2 centers.
9+ * 2. Root both trees at their center(s) and compute a canonical string
10+ * encoding via DFS. Each subtree is encoded as "(children...)" with
11+ * children sorted lexicographically so that isomorphic subtrees
12+ * produce identical strings.
13+ * 3. Compare the encodings. If tree2 has two centers, try both — if
14+ * either matches tree1's encoding, the trees are isomorphic.
15+ *
16+ * Can easily be adapted for rooted tree isomorphism by skipping step 1
17+ * and encoding directly from the given roots.
18+ *
19+ * Tested against: https://uva.onlinejudge.org/external/124/p12489.pdf
20+ *
21+ * Time: O(V * log(V)) — dominated by sorting child encodings at each node
22+ * Space: O(V)
623 *
724 * @author William Fiset, william.alexandre.fiset@gmail.com
825 */
926package com .williamfiset .algorithms .graphtheory .treealgorithms ;
1027
11- import java .util .*;
28+ import java .util .ArrayList ;
29+ import java .util .Collections ;
30+ import java .util .List ;
1231
1332public class TreeIsomorphism {
1433
1534 public static class TreeNode {
16- private int id ;
17- private TreeNode parent ;
18- private List <TreeNode > children ;
35+ private final int id ;
36+ private final TreeNode parent ;
37+ private final List <TreeNode > children ;
1938
20- // Useful constructor for root node.
2139 public TreeNode (int id ) {
2240 this (id , /* parent= */ null );
2341 }
2442
2543 public TreeNode (int id , TreeNode parent ) {
2644 this .id = id ;
2745 this .parent = parent ;
28- children = new LinkedList <>();
46+ this . children = new ArrayList <>();
2947 }
3048
3149 public void addChildren (TreeNode ... nodes ) {
@@ -52,7 +70,10 @@ public String toString() {
5270 }
5371 }
5472
55- // Determines if two unrooted trees are isomorphic
73+ /**
74+ * Returns true if the two unrooted trees are isomorphic.
75+ * Roots each tree at its center(s) and compares canonical encodings.
76+ */
5677 public static boolean treesAreIsomorphic (List <List <Integer >> tree1 , List <List <Integer >> tree2 ) {
5778 if (tree1 .isEmpty () || tree2 .isEmpty ()) {
5879 throw new IllegalArgumentException ("Empty tree input" );
@@ -75,6 +96,10 @@ public static boolean treesAreIsomorphic(List<List<Integer>> tree1, List<List<In
7596 return false ;
7697 }
7798
99+ /**
100+ * Finds the center node(s) of the tree by iteratively removing leaf nodes.
101+ * A tree has either 1 center (odd diameter) or 2 centers (even diameter).
102+ */
78103 private static List <Integer > findTreeCenters (List <List <Integer >> tree ) {
79104 int n = tree .size ();
80105
@@ -116,45 +141,45 @@ private static TreeNode rootTree(List<List<Integer>> graph, int rootId) {
116141 return buildTree (graph , root );
117142 }
118143
119- // Do dfs to construct rooted tree.
144+ /** Recursively builds the rooted tree via DFS, skipping the edge back to parent. */
120145 private static TreeNode buildTree (List <List <Integer >> graph , TreeNode node ) {
121146 for (int neighbor : graph .get (node .id ())) {
122- // Ignore adding an edge pointing back to parent.
123147 if (node .parent () != null && neighbor == node .parent ().id ()) {
124148 continue ;
125149 }
126-
127150 TreeNode child = new TreeNode (neighbor , node );
128151 node .addChildren (child );
129-
130152 buildTree (graph , child );
131153 }
132154 return node ;
133155 }
134156
135- // Constructs the canonical form representation of a tree as a string.
157+ /**
158+ * Constructs a canonical string encoding of the subtree rooted at the given node.
159+ * Children encodings are sorted lexicographically so that isomorphic subtrees
160+ * always produce the same string. Example: "((()())())" for a small tree.
161+ */
136162 public static String encode (TreeNode node ) {
137163 if (node == null ) {
138164 return "" ;
139165 }
140- List <String > labels = new LinkedList <>();
166+ List <String > labels = new ArrayList <>();
141167 for (TreeNode child : node .children ()) {
142168 labels .add (encode (child ));
143169 }
144170 Collections .sort (labels );
145- StringBuilder sb = new StringBuilder ();
171+ StringBuilder sb = new StringBuilder ("(" );
146172 for (String label : labels ) {
147173 sb .append (label );
148174 }
149- return "(" + sb .toString () + ")" ;
175+ return sb .append ( ")" ). toString () ;
150176 }
151177
152- /* Graph/Tree creation helper methods. */
178+ /* Graph helpers */
153179
154- // Create a graph as a adjacency list with 'n' nodes.
155180 public static List <List <Integer >> createEmptyGraph (int n ) {
156181 List <List <Integer >> graph = new ArrayList <>(n );
157- for (int i = 0 ; i < n ; i ++) graph .add (new LinkedList <>());
182+ for (int i = 0 ; i < n ; i ++) graph .add (new ArrayList <>());
158183 return graph ;
159184 }
160185
@@ -163,15 +188,23 @@ public static void addUndirectedEdge(List<List<Integer>> graph, int from, int to
163188 graph .get (to ).add (from );
164189 }
165190
166- /* Example usage */
191+ // ==================== Main ====================
167192
168193 public static void main (String [] args ) {
169194 simpleIsomorphismTest ();
170195 testEncodingTreeFromSlides ();
171196 }
172197
173- // Test if two tree are isomorphic, meaning they are structurally equivalent
174- // but are labeled differently.
198+ // tree1 (rooted at center 2): tree2 (rooted at center 1):
199+ //
200+ // 2 1
201+ // / | \ / | \
202+ // 0 1 3 0 3 2
203+ // | |
204+ // 4 4
205+ //
206+ // Both are isomorphic — same structure, different labels.
207+ //
175208 private static void simpleIsomorphismTest () {
176209 List <List <Integer >> tree1 = createEmptyGraph (5 );
177210 addUndirectedEdge (tree1 , 2 , 0 );
@@ -185,11 +218,22 @@ private static void simpleIsomorphismTest() {
185218 addUndirectedEdge (tree2 , 1 , 3 );
186219 addUndirectedEdge (tree2 , 1 , 2 );
187220
188- if (!treesAreIsomorphic (tree1 , tree2 )) {
189- System .out .println ("Oops, these tree should be isomorphic!" );
190- }
221+ // true
222+ System .out .println ("Isomorphic: " + treesAreIsomorphic (tree1 , tree2 ));
191223 }
192224
225+ // Rooted at node 0:
226+ //
227+ // 0
228+ // / | \
229+ // 2 1 3
230+ // / \ / \ \
231+ // 6 7 4 5 8
232+ // |
233+ // 9
234+ //
235+ // Canonical encoding: (((())())(()())(()))
236+ //
193237 private static void testEncodingTreeFromSlides () {
194238 List <List <Integer >> tree = createEmptyGraph (10 );
195239 addUndirectedEdge (tree , 0 , 2 );
@@ -204,8 +248,7 @@ private static void testEncodingTreeFromSlides() {
204248
205249 TreeNode root0 = rootTree (tree , 0 );
206250
207- if (!encode (root0 ).equals ("(((())())(()())(()))" )) {
208- System .out .println ("Tree encoding is wrong: " + encode (root0 ));
209- }
251+ // (((())())(()())(()))
252+ System .out .println ("Encoding: " + encode (root0 ));
210253 }
211254}
0 commit comments