一,问题介绍
本文章讨论两个问题:
①如何判断两棵二叉树的结构是一样的、对应的每个结点都有着相同的值。--即判断两棵二叉树是一样的
②给定两棵二叉树,如何判断一棵二叉树是另一棵二叉树的子结构
③给定两棵二叉树,如何判断一棵二叉树是另一棵二叉树的子树
注意,子结点与子树有那么一点点不同。
上面的二叉树B 是二叉树A 的子结构,但是不能说是二叉树A的子树。但是二叉树C 是 二叉树A的子树。
二,问题分析
1,如何判断两棵二叉树的结构是一样的、且对应的每个结点都有着相同的值。
对于①如何判断两棵二叉树的结构是一样的、对应的每个结点都有着相同的值
有两种方法:一种是递归比较。另一种是二叉树的遍历。
先说二叉树的遍历。由于先序遍历 再加上 中序遍历能唯一确定一棵二叉树。故,对这两棵树分别进行先序和中序遍历,比较这两棵树的先序遍历序列和中序遍历序列,如果都一样则说明这两棵二叉树是一样的。这里用了两次遍历。时间复杂度为O(2N)
由于二叉树的中序遍历和先序/后序遍历比较容易,故不用代码实现了。
下面来看如何用递归来判断两棵二叉树是不是一样的。
我们的思路如下:首先比较根结点是不是一样的;如果根结点一样,再继续比较根的左右孩子是不是一样的,这是一个递归过程。
1 public boolean sameTree2(BinaryNode<T> root1, BinaryNode<T> root2){ 2 //树的结构不一样 3 if((root1 == null && root2 != null) || (root1 != null && root2 == null)) 4 return false; 5 6 //两棵树最终递归到终点时 7 if(root1 == null && root2 == null) 8 return true; 9 10 if(root1.element.compareTo(root2.element) != 0) 11 return false; 12 else 13 return sameTree2(root1.left, root2.left) && sameTree2(root1.right, root2.right); 14 }
第3行的if语句是输入的两棵树的初始情况判断。
第6行的理解如下:若两棵树是一样的,那么它们不断递归比较结点,最终二棵树的叶子结点都比较完了,即都比较到了空结点,此时它们就是相同的。
第10行到第13行则是整个正常的递归过程:先判断当前的根结点是不是一样的,如果不是直接返回false(第11行),如果当前的根结点是一样的,则继续递归比较当前根结点的左子树和右子树(第13行),只有当左右子树都是true是, 位与(&&)操作才返回true。
这种方式对两棵二叉树只遍历了一次就可以判断两棵树是否相同,而且当树不相同时,是不需要遍历完整棵树的(第10行if成立时,立即return)。相比于,上面提到的中序遍历加先序遍历,不管二棵树是否相同,都需要遍历 整棵树。
故这种递归方法的最坏时间复杂度为O(N)
2,给定两棵二叉树,如何判断一棵二叉树B是另一棵二叉树A的子结构
个人感觉判断子结构要比判断子树困难一点,如果是子树,那么它一定是子结构;但是反过来不成立。
判断子结构的核心思路其实与 (1) 中判断两棵树是否相同 是一致的。只不过对于 (1) 而言, 只要有一个结点不相同了,那这二棵二叉树就不是一样的了。而对于子结构而言,有可能某个结点的子树中包含了 子结构。
因此,需要遍历二叉树A中的所有结点,检查当前遍历的结点为根的树中是否包含了二叉树B。比如下图:
首先遍历二叉树A的根结点8,由于二叉树B的根为4,两者不同。此时我们可以判断出这两棵树是不相同的。
但是,我们还不能判断出二叉树B 不是 二叉树A 的子结构。还需要进一步判断。这个判断,就是一个递归过程了。递归如下:
判断二叉树A的根结点的左子树是否包含了二叉树B,若未包含,再判断二叉树A的根结点的右子树是否包含了二叉树B
1 /** 2 * 判断 以root2为根的树是否是 root1 为根的树 的 子树 3 * @param root1 4 * @param root2 5 * @return 6 */ 7 public boolean isSubTree(BinaryNode<T> root1, BinaryNode<T> root2){ 8 boolean result = false; 9 10 //只有root1 和 root2 都不为空时,才去判断.其他情况root2都不是root1的子树 11 if(root1 != null && root2 != null){ 12 if(root1.element.compareTo(root2.element) == 0) 13 result = hasSameNode(root1, root2); 14 if(!result) 15 result = isSubTree(root1.left, root2);//递归遍历左子树 16 if(!result) 17 result = isSubTree(root1.right, root2);//递归遍历右子树 18 } 19 return result; 20 }
说白了,整个递归判断的结构就类似于二叉树的递归的先序遍历结构。
首先先序遍历二叉树A中(根结点)每一个结点(第11行至第13行),如果遍历到的该结点与二叉树B的根相同,则比较它们的孩子结点是否相同(hasSameNode())。
若不同(第14行if成立),相当于先序遍历左子树,判断该结点的左孩子为根的子树是否包含了二叉树B
若还不相同(result返回false,从而第16行if判断成立),相当于先序遍历右子树,判断该结点的右孩子为根的子树是否包含了二叉树B
从上面可以看出,先序遍历二叉树A中每个结点。然后以该结点为根的子树与二叉树B中的结点进行一 一比较。故整个时间复杂度为O(NK)
其中,N是二叉树A中结点的个数,K是二叉树B中结点的个数。
3,给定两棵二叉树,如何判断一棵二叉树是另一棵二叉树的子树
从前面的 (2) 可知,我们可以用判断子结构的实现,来判断子树。
若二叉树B是二叉树A的子结构,且二叉树A的根结点 与 二叉树B的根结点不相同。那么,二叉树B就是二叉树A的子树了。
故这种方法的时间复杂度也为O(NK)
此外,还有另外一种方法:可将该问题转化为串的匹配问题。如果二叉树是B是二叉树A的子树,
则二叉树B的中序遍历序列 (前序、后序应该也是可以的吧)是 二叉树A的 中序遍历 序列的一个子串。
从而,可以用KMP算法实现字符串匹配。如果匹配成功,则说明二叉树B是二叉树A的子树,反之则不是。
关于如何判断子树的代码,我就不实现了。
三,完整代码实现
①如何判断两棵二叉树的结构是一样的、对应的每个结点都有着相同的值。--即判断两棵二叉树是一样的
②给定两棵二叉树,如何判断一棵二叉树是另一棵二叉树的子结构
上面两个问题的完整版代码实现如下:
1 public class SubTree<T extends Comparable<? super T>> { 2 private static class BinaryNode<T> { 3 T element; 4 BinaryNode<T> left; 5 BinaryNode<T> right; 6 7 public BinaryNode(T element) { 8 this(element, null, null); 9 } 10 11 public BinaryNode(T element, BinaryNode<T> left, BinaryNode<T> right) { 12 this.element = element; 13 this.left = left; 14 this.right = right; 15 } 16 17 public String toString() { 18 return element.toString(); 19 } 20 } 21 22 private BinaryNode<T> root; 23 24 public void insert(T ele) { 25 root = insert(ele, root);// 每次插入操作都会'更新'根节点. 26 } 27 28 private BinaryNode<T> insert(T ele, BinaryNode<T> root) { 29 if (root == null) 30 return new BinaryNode<T>(ele); 31 int compareResult = ele.compareTo(root.element); 32 if (compareResult > 0) 33 root.right = insert(ele, root.right); 34 else if (compareResult < 0) 35 root.left = insert(ele, root.left); 36 else 37 ; 38 return root; 39 } 40 41 /** 42 * 判断 以root2为根的树是否是 root1 为根的树 的 子树 43 * @param root1 44 * @param root2 45 * @return 46 */ 47 public boolean isSubTree(BinaryNode<T> root1, BinaryNode<T> root2){ 48 boolean result = false; 49 50 //只有root1 和 root2 都不为空时,才去判断.其他情况root2都不是root1的子树 51 if(root1 != null && root2 != null){ 52 if(root1.element.compareTo(root2.element) == 0) 53 result = hasSameNode(root1, root2); 54 if(!result) 55 result = isSubTree(root1.left, root2); 56 if(!result) 57 result = isSubTree(root1.right, root2); 58 } 59 return result; 60 } 61 62 //比较两棵树是否有相同的结点 63 private boolean hasSameNode(BinaryNode<T> root1, BinaryNode<T> root2){ 64 65 //base condition 66 if(root2 == null)//hasSameNode最初被调用时 root2 != null 67 return true; 68 if(root1 == null) 69 return false; 70 71 //verify Node has the same value 72 if(root1.element.compareTo(root2.element) != 0) 73 return false; 74 return hasSameNode(root1.left, root2.left) && hasSameNode(root1.right, root2.right); 75 } 76 77 public boolean sameTree2(BinaryNode<T> root1, BinaryNode<T> root2){ 78 //树的结构不一样 79 if((root1 == null && root2 != null) || (root1 != null && root2 == null)) 80 return false; 81 82 //两棵树最终递归到终点,说明它们是一样的 83 if(root1 == null && root2 == null) 84 return true; 85 86 if(root1.element.compareTo(root2.element) != 0) 87 return false; 88 else 89 return sameTree2(root1.left, root2.left) && sameTree2(root1.right, root2.right); 90 } 91 92 //for test purpose 93 public static void main(String[] args) { 94 95 int[] ele = {1,2,3,4,5}; 96 SubTree<Integer> tree1 = new SubTree<Integer>(); 97 for (int i : ele) { 98 tree1.insert(i); 99 } 100 101 int[]ele2 = {1,2,3,4,5}; 102 SubTree<Integer> tree2 = new SubTree<Integer>(); 103 for (int i : ele2) { 104 tree2.insert(i); 105 } 106 107 System.out.println(tree1.isSubTree(tree1.root, tree2.root)); 108 System.out.println(tree1.sameTree2(tree1.root, tree2.root)); 109 } 110 }
本文转自hapjin博客园博客,原文链接:http://www.cnblogs.com/hapjin/p/5559688.html,如需转载请自行联系原作者