一,問題介紹算法
本文章討論兩個問題: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 }