@@ -762,61 +762,172 @@ and subscribe for more mathematics and computer science videos.
762
762
Isomorphism source code
763
763
764
764
Hello and welcome, my name is William, today we're going to take a
765
- look at some source code for identifying isomorphic trees.
765
+ look at some source code for identifying isomorphic trees which will also
766
+ include the source code for rooting a tree and finding the center of a tree.
766
767
767
768
In the previous video, we talked about identifying isomorphic trees by first
768
769
serializing the tree into a string and then using that as a means for equality.
769
- Today we will be looking at the same approach in more details , so make sure
770
+ Today we will be looking at the same approach in more detail , so make sure
770
771
you've given the previous video a watch if you haven't done so. There should be
771
772
a link in the description below.
772
773
773
774
All the source code you see today can be found on Github at github dot com slash
774
- william fiset slash algorithms. Alright, let's jump in.
775
+ william fiset slash algorithms. Alright, buckle up because even though we'll
776
+ only be looking at around 150 lines of code there's a lot going on,
777
+ let's jump in.
775
778
776
- Alright, here we are in the source code for identifying isomorphic trees written
779
+ So here we are in the source code for identifying isomorphic trees written
777
780
in Java.
778
781
779
- The first thing I do is define a TreeNode object we'll be using to represent our
782
+
783
+ ---
784
+
785
+ treesAreIsomorphic
786
+
787
+ The first interesting method is the treesAreIsomorphic method which takes as
788
+ input two undirected trees stored as adjacency lists: tree1 and tree2.
789
+
790
+ Just so that we don't have to worry about it, I check if either trees are empty
791
+ and throw an exception.
792
+
793
+ After that, we begin the whole tree serialzation process, it's pretty simple,
794
+ start by finding the center or centers of both trees.
795
+
796
+ Then encode the first tree into a string. The encode method takes as input a
797
+ rooted tree instead of an undirected tree so make sure you root the tree first.
798
+ After the encode method has finished processing it will return a unique string
799
+ for tree1.
800
+
801
+ Now the next thing we want to do is encode the second tree so that we can
802
+ compare it to the first one. However, if the second tree has 2 centers we
803
+ don't know which center node in the second tree is the correct one, so we need
804
+ to try both.
805
+ For this, iterate over both centers and root the tree comparing the
806
+ encoded result with the first tree . If there's any match then we
807
+ know that the trees are isomorphic.
808
+ Awesome so that's the algorithm from a bird's eye view, now let's dig into the
809
+ details and figure out what the heck is going on inside the findTreeCenters,
810
+ rootTree and encode methods.
811
+
812
+ ---
813
+
814
+ findTreeCenters
815
+
816
+ Let's begin with the findTreeCenters method, this is the source code from one
817
+ video ago. To refresh your memory, the approach we're taking to find the center
818
+ or centers of a tree is to iteratively remove leaf nodes layer by layer. This is
819
+ analogous to peeling the layers of an onion, when we're finished removing all
820
+ the layers what's left is the middle.
821
+
822
+ The integer 'n' represents the number of nodes in our tree.
823
+
824
+ After this, I define two variables. The first one is 'degree' with size 'n'
825
+ which will capture the degree of each node, followed by 'leaves' which is a
826
+ dynamic array that will contains most recent layer of leaf nodes.
827
+
828
+ Then for the first bit of logic, simply loop through all the nodes and compute
829
+ the degree of each one. I do this by inspecting the adjacency list and counting
830
+ the number of edges coming out of each node.
831
+
832
+ Then, if the node has a degree less than or equal to 1, meaning we're either
833
+ dealing with a single node tree or a leaf node then add the node to the leaves
834
+ array. After this, set the degree of the leaf node to be zero as though we
835
+ removed it from the tree.
836
+
837
+ The way we're going to know when we've found the center or centers is when we
838
+ have processed all the nodes in the tree. The variable processedLeafs is going
839
+ to keep track how many nodes we've processed so far. Every iteration we're going
840
+ to increment processedLeafs by the number of leaves we found in the last layer.
841
+
842
+ Entering the loop, 'new_leaves' is a new array that will contain the new leaf
843
+ nodes on the next layer. I'm using a new array to avoid interfering with the
844
+ leaf nodes on the current layer.
845
+
846
+ So for every leaf node on the current layer,
847
+
848
+ process all the neighbors of those nodes
849
+
850
+ and decrement the degree of the neighbor nodes. Since we're removing the current
851
+ node this means that the degree of the neighboring nodes needs to go down by 1.
852
+ If the neighbor node after being decremented has a degree of 1 then we know it
853
+ will be in the next layer of leaf nodes so add it to the new_leaves array.
854
+
855
+ Every time we finish processing a former leaf node, give it a degree of zero to
856
+ mark it as removed.
857
+
858
+ When we've finished processing the current layer, increment the processedLeafs
859
+ variable and replace the leaves array with the new leaves.
860
+
861
+ And finally return the centers which are stored in the leaves array.
862
+
863
+ So that's how the findTreeCenters method works, now before we take a look at how
864
+ to root a tree we should look at the TreeNode class which gets used by the
865
+ rootTree method.
866
+
867
+ ---
868
+
869
+ TreeNode class
870
+
871
+ The first thing I do is define this TreeNode object we'll be using to represent our
780
872
rooted trees. TreeNode's are straightforward, they only contain three members,
781
873
a unique id, a parent node reference and a list of references to all its children.
782
874
783
875
To create a TreeNode, all you need is to give that TreeNode a unique id, you can
784
- also optionally create a TreeNode with an id and a parent node.
876
+ also optionally create a TreeNode with an id and a specified parent node.
785
877
786
878
The one useful method I've added to this class is the ability to add children
787
879
to this TreeNode. Simply pass in any number of TreeNodes you wish to add as child
788
880
node to this node and they'll get added to the children's list.
789
881
790
882
The rest of the methods in this class are just getter methods.
791
883
792
- The next interesting method is the treesAreIsomorphic method which takes as
793
- input two undirected tree stored as adjacency lists tree1 and tree2.
884
+ ---
794
885
795
- Just so that we don't have to worry about it, I check if either trees are empty
796
- and throw an exception.
886
+ rootTree
797
887
798
- After that, I find the center or centers of both trees .
888
+ Coming back to the rootTree method.. .
799
889
800
- TODO(waf): describe the whole flow here before moving on,
890
+ You'll see that this function takes as input our tree represented as an
891
+ adjacency list and the id of the root we want to root the tree by. At lot of the
892
+ time the root id is going to be zero, but it's nice to be able to choose which
893
+ node will be the root in situations where it matters such as this tree
894
+ isomorphism problem.
801
895
896
+ The first line in the rootTree method creates the root TreeNode.
802
897
803
- Then I root tree1 using its first center node.
804
- Afterwhich I loop through bot of tree2's centers and do the isomorphism check.
805
- You need to
898
+ Then I call the buildTree method to start the depth first traversal to root the
899
+ tree. As input parameters I pass in the graph and the root node as the current
900
+ node.
806
901
807
- Now the next thing we want to do is encode the
808
- second tree and compare the encoded results, but if the tree has 2 centers we
809
- don't know which center node in the second tree is the correct one, so we need
810
- to try both. For this, iterate over both centers and root the tree comparing the
811
- encoded result with the one from the first tree. If there's any match then we
812
- know the trees are isomorphic.
902
+ Inside the buildTree method we enter a for loop that loops over all the
903
+ neighbors of the current node. All these neighbor nodes are going to become the
904
+ children of the current node, except for the last parent node.
813
905
906
+ One thing we need to be aware of is that edges are undirected in the original
907
+ tree meaning we absolutely need to avoid the situation where we add a directed
908
+ edge pointing back to the current node's parent, every other neighbor node will
909
+ become a child of the current node.
814
910
911
+ To check if the neighbor node is the parent node first check that the parent is
912
+ not null so we don't get a null pointer exception, then access the parent's id
913
+ and check if the child id is equal to the parent id and skip this node if true.
815
914
915
+ Otherwise we're dealing with a proper child node, so create a new node and add
916
+ the child TreeNode to the list of the current node's children.
917
+
918
+ Afterwards, dig deeper into the tree and do the same thing but for the newly
919
+ created child node.
920
+
921
+ Once we have finished iterating over all the neighbors of this node return the
922
+ current node.
816
923
924
+ ---
817
925
926
+ encode
818
927
928
+ ---
819
929
930
+ testing.
820
931
821
932
822
933
0 commit comments