比较两棵二叉树--(比较两棵二叉树是否相同/判断一棵二叉树是否是另一棵二叉树的子树)...

一,问题介绍

本文章讨论两个问题:

①如何判断两棵二叉树的结构是一样的、对应的每个结点都有着相同的值。--即判断两棵二叉树是一样的

②给定两棵二叉树,如何判断一棵二叉树是另一棵二叉树的子结构

③给定两棵二叉树,如何判断一棵二叉树是另一棵二叉树的子树

注意,子结点与子树有那么一点点不同。

上面的二叉树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,如需转载请自行联系原作者
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值