比較兩棵二叉樹--(比較兩棵二叉樹是否相同/判斷一棵二叉樹是不是另外一棵二叉樹的子樹)

一,問題介紹算法

本文章討論兩個問題:this

①如何判斷兩棵二叉樹的結構是同樣的、對應的每一個結點都有着相同的值。--即判斷兩棵二叉樹是同樣的spa

②給定兩棵二叉樹,如何判斷一棵二叉樹是另外一棵二叉樹的子結構code

③給定兩棵二叉樹,如何判斷一棵二叉樹是另外一棵二叉樹的子樹blog

注意,子結點與子樹有那麼一點點不一樣。遞歸

上面的二叉樹B 是二叉樹A 的子結構,可是不能說是二叉樹A的子樹。可是二叉樹C 是 二叉樹A的子樹。element

 

二,問題分析字符串

1,如何判斷兩棵二叉樹的結構是同樣的、且對應的每一個結點都有着相同的值。it

對於①如何判斷兩棵二叉樹的結構是同樣的、對應的每一個結點都有着相同的值io

有兩種方法:一種是遞歸比較。另外一種是二叉樹的遍歷。

先說二叉樹的遍歷。因爲先序遍歷 再加上 中序遍歷能惟一肯定一棵二叉樹。故,對這兩棵樹分別進行先序和中序遍歷,比較這兩棵樹的先序遍歷序列和中序遍歷序列,若是都同樣則說明這兩棵二叉樹是同樣的。這裏用了兩次遍歷。時間複雜度爲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 }
相關文章
相關標籤/搜索