1. 程式人生 > >如何判斷一棵樹是不是另一棵樹的子樹

如何判斷一棵樹是不是另一棵樹的子樹

情況一:兩棵樹均是有序的,即樹的左右子樹的順序是固定的

分析:假設這兩棵樹中的第一棵為母樹,另一棵為子樹。首先在母樹中搜索子樹的根節點,找到根節點之後就按照該根節點向下搜尋比較,如果比較結果為true即子樹是母樹的一棵子樹,否則的話繼續向下搜尋子樹根節點在母樹中的位置,如此遞迴下去最終即可得到結果。

package suanfaTest;

class TreeNode{
    int value;
    TreeNode leftChild=null;
    TreeNode rightChild=null;
    TreeNode() {}
    TreeNode(int value){
        this.value=value;
    }
    TreeNode(int value,TreeNode leftChild,TreeNode rightChild){
        this.value=value;
        this.leftChild=leftChild;
        this.rightChild=rightChild;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public TreeNode getLeftChild() {
        return leftChild;
    }
    public TreeNode getRightChild() {
        return rightChild;
    }
    public void setLeftChild(TreeNode leftChild) {
        this.leftChild = leftChild;
    }
    public void setRightChild(TreeNode rightChild) {
        this.rightChild = rightChild;
    }
}
public class TreeChild {

    // 搜尋子樹的根節點在母樹中的節點的位置,並檢查是否相等
    static boolean searchParentForChildRoot(TreeNode parentRoot, TreeNode childRoot) {
        boolean result = false;
        if (parentRoot != null && childRoot != null) {
            if (childRoot.getValue() == parentRoot.getValue()) 
                result=compareParentAndChildTree(parentRoot,childRoot);
            if(!result)
                result=compareParentAndChildTree(parentRoot.getLeftChild(),childRoot);
            if(!result)
                result=compareParentAndChildTree(parentRoot.getRightChild(), childRoot);
        }
        return result;
    }

    // 比較以得到的和子樹根節點相同的節點的子樹和原始子樹,若相同則返回true,否則返回false
    static boolean compareParentAndChildTree(TreeNode parentRoot, TreeNode childRoot) {
        if (childRoot == null)// 結束條件為子樹節點為空
            return true;
        if (parentRoot == null)
            return false;
        if (childRoot.getValue() != parentRoot.getValue()) {
            return false;
        } else 
            return compareParentAndChildTree(parentRoot.getLeftChild(), childRoot.getLeftChild()) == true&& compareParentAndChildTree(parentRoot.getRightChild(), childRoot.getRightChild()) == true;
    }

    //static boolean childTree
    public static void main(String[] args) {
        TreeNode paremtRoot=new TreeNode(8);
        TreeNode rootChild1=new TreeNode(8);
        TreeNode rootChild2=new TreeNode(7);
        TreeNode child1Child1=new TreeNode(9);
        TreeNode child1Child2=new TreeNode(2);
        TreeNode child12Child1=new TreeNode(4);
        TreeNode child12Child2=new TreeNode(7);

        paremtRoot.setLeftChild(rootChild1);
        paremtRoot.setRightChild(rootChild2);
        rootChild1.setLeftChild(child1Child1);
        rootChild1.setRightChild(child1Child2);
        child1Child2.setLeftChild(child12Child1);
        child1Child2.setRightChild(child12Child2);

        TreeNode childRoot=new TreeNode(8);
        TreeNode crootChild1=new TreeNode(9);
        TreeNode crootChild2=new TreeNode(2);
        childRoot.setLeftChild(crootChild1);
        childRoot.setRightChild(crootChild2);

        System.out.println(searchParentForChildRoot(paremtRoot,childRoot));
    }

}

結果為true;

情況二:假設T1是一棵含有幾百萬個節點的樹,T2含有幾百個節點。判斷T2是否是T1 的子樹。

分析:

首先考慮小資料量的情況,可以根據樹的前序和中序遍歷所得的字串,來通過判斷T2生成的字串是否是T1字串的子串,來判斷T2是否是T1的子樹。假設T1的節點數為N,T2的節點數為M。遍歷兩棵樹演算法時間複雜性是O(N + M), 判斷字串是否為另一個字串的子串的複雜性也是O( N + M)(比如使用KMP演算法)。所需要的空間也是O(N + M)。

這裡有一個問題需要注意:對於左節點或者右節點為null的情況,需要在字串中插入特殊字元表示。否則對於下面這種情形將會判斷錯誤:

因此如果插入特殊字元,上述兩棵樹的中序和前序遍歷的結果是相同的。

由於本例有幾百萬的節點,需要佔用O(N + M)的記憶體。

如果換一種思路,就是遍歷T1,每當T1的某個節點與T2的根節點值相同時,就判斷兩棵子樹是否相同。這個演算法的複雜度是O(N*M)。我們再仔細思考一下。因為只有在節點值與T2的根節點值相同才會呼叫O(M)。假設有K次這種情況,那麼演算法的複雜度就是O(N + K*M)。

struct TreeNode{  
    TreeNode *leftChild;  
    TreeNode *rightChild;  
    int data;  
};  
// check sub tree n1 == sub tree n2  
 bool checkSubTree(const TreeNode* n1, const TreeNode* n2){  
    if( n1 == nullptr && n2 == nullptr )  
        return true;  
      
    if( n1 == nullptr || n2 == nullptr )  
        return false;  
      
    if( n1->data != n2->data )  
        return false;  
      
    return checkSubTree(n1->leftChild, n2->leftChild) && checkSubTree(n1->rightChild, n2->rightChild);  
}  
bool subTree(const TreeNode *n1, const TreeNode *n2){  
    if( n1 == nullptr){  
        return false; // the bigger tree is empty, so t2 is not subtree of t1  
    }  
      
    if( n1->data == n2->data){  
        if( checkSubTree(n1, n2))  
            return true;  
    }  
      
    return subTree(n1->leftChild, n2) || subTree(n2->rightChild, n2);  
}  

對於上面討論的2種解法,哪種解法比較好呢?其實有必要好好討論一番:

1)方法一會佔用O(N + M)的記憶體,而另外一種解法只會佔用O(logN + logM)的記憶體(遞迴的棧記憶體)。當考慮scalability擴充套件性時,記憶體使用的多寡是個很重要的因素。

2)方法一的時間複雜度為O(N + M),方法二最差的時間複雜度是O(N*M)。所以要通過工程實踐或者是歷史資料看一下哪種方法更優。當然了,方法二也可能會很早發現兩棵樹的不同,早早的退出了checkSubTree。

總的來說,在空間使用上,方法二更好。在時間上,需要通過實際資料來驗證。

 

原文地址:https://blog.csdn.net/hutongling/article/details/63684262

                  https://www.cnblogs.com/anzhsoft/p/3602981.html