n個結點,不同形態的二叉樹(數目+生成)


題目鏈接:

  不同的二叉查找樹:http://www.lintcode.com/zh-cn/problem/unique-binary-search-trees/

  不同的二叉查找樹 II:http://www.lintcode.com/zh-cn/problem/unique-binary-search-trees-ii/

不同形態二叉樹的數目:

樣例

  給出n = 3,有5種不同形態的二叉查找樹:

  1           3    3       2      1
   \         /    /       / \      \
    3      2     1       1   3      2
   /      /       \                  \
  2     1          2                  3

分析

   可以分析,當n=1時,只有1個根節點,則只能組成1種形態的二叉樹,令n個節點可組成的二叉樹數量表示為h(n),則h(1)=1; h(0)=0;

       當n=2時,1個根節點固定,還有2-1個節點。這一個節點可以分成(1,0),(0,1)兩組。即左邊放1個,右邊放0個;或者左邊放0個,右邊放1個。即:h(2)=h(0)*h(1)+h(1)*h(0)=2,則能組成2種形態的二叉樹。

      當n=3時,1個根節點固定,還有2個節點。這2個節點可以分成(2,0),(1,1),(0,2)3組。即h(3)=h(0)*h(2)+h(1)*h(1)+h(2)*h(0)=5,則能組成5種形態的二叉樹。

以此類推,當n>=2時,可組成的二叉樹數量為h(n)=h(0)*h(n-1)+h(1)*h(n-2)+...+h(n-1)*h(0)種,即符合Catalan數的定義,可直接利用通項公式得出結果。

令h(1)=1,h(0)=1,catalan數(卡特蘭數)滿足遞歸式: 

  h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (其中n>=2)

  另類遞歸式:

     h(n)=((4*n-2)/(n+1))*h(n-1);

  該遞推關系的解為:

  h(n)=C(2n,n)/(n+1) (n=1,2,3,...)

  由此想到了上次說的"N個數依次入棧,出棧順序有多少種?",  同樣用的也是卡特蘭數。

   http://www.cnblogs.com/hujunzheng/p/4845354.html

代碼

class Solution {
public:
    /**
     * @paramn n: An integer
     * @return: An integer
     */
    long long C(int n, int m){
        n = n-m+1;
        long long ans = 1;
        for(int i=1; i<=m; ++i){
            ans *= n++;
            ans /= i;
        }
        return ans;
    }
    int numTrees(int n) {
        // write your code here
        return C(2*n, n)/(n+1);
    }
};

 

構建不同形態二叉樹:

樣例

  給出n = 3,生成所有5種不同形態的二叉查找樹:

  1         3     3       2    1
   \       /     /       / \    \
    3     2     1       1   3    2
   /     /       \                \
  2     1         2                3

  其實通過樣例,我們可以發現n個結點構造不同形態二叉樹的過程,1,2,3.....n個結點,枚舉每一個結點為根結點(假設為root, 1<=root<=n), 那么(1,2..root-1)和(root+1, root+2...n)分別是root的左右子樹。每一步不斷地重復上述過程,最終會得到所有形態的二叉樹。

算法實現

  先弱弱的說一下自己錯誤的實現,因為遞歸實現的時候會得到不同的二叉樹,那么如何判斷n個結點正好生成了二叉樹呢?於是用了一個變量useNode(=0),表示當前已經用了多少個結點建樹。當useNode等於n的時候說明產生了一棵符合要求的樹,接着拷貝一下剛才生成的樹,然后放入vector中,繼續建造下一棵符合條件的二叉樹。

錯誤代碼:

/**
 * Definition of TreeNode:
 * class TreeNode {
 * public:
 *     int val;
 *     TreeNode *left, *right;
 *     TreeNode(int val) {
 *         this->val = val;
 *         this->left = this->right = NULL;
 *     }
 * }
 */
class Solution {
public:
    /**
     * @paramn n: An integer
     * @return: A list of root
     */
    vector<TreeNode *> ans;
    int cntNode=0;//節點的總數
    TreeNode *curRoot = NULL;
    
    void copyT(TreeNode * &tmp, TreeNode *T){
        if(T){
            tmp = new TreeNode(T->val);
            copyT(tmp->left, T->left);
            copyT(tmp->right, T->right);
        }
    }
    
    void buildT(TreeNode * &T, int ld, int rd, int useNode){
        if(ld > rd) return;
        for(int root=ld; root<=rd; ++root){
            T = new TreeNode(root);
            if(ld==1 && rd==cntNode)
                curRoot = T;
            if(useNode+1==cntNode){//這個樹已經建立完畢,拷貝一下吧
                TreeNode *tmp = NULL;
                copyT(tmp, curRoot);
                ans.push_back(tmp);
            }
            buildT(T->left, ld, root-1, useNode+1);
            buildT(T->right, root+1, rd, useNode+root-ld+1);
        }
    }
    vector<TreeNode *> generateTrees(int n) {
        // write your code here
        cntNode = n;
        TreeNode *T = NULL;
        buildT(T, 1, n, 0);
        if(n == 0) ans.push_back(T);
        return ans;
    }
};

  后來運行之后,看到錯誤的答案與正確答案的對比,如下:

  當n=4的時候

  輸出

[{1,#,2,#,3,#,4},{1,#,2,#,4,3},{1,#,3,2,4},{1,#,4,2,#,#,3},{1,#,4,3,#,2},{2,1,3,#,#,#,4},{2,1,4,#,#,3},{3,2,4,1},{4,1,#,#,2,#,3},{4,1,#,#,3,2},{4,2,#,1,3},{4,3,#,1,#,#,2},{4,3,#,2,#,1}]

  期望答案

[{1,#,2,#,3,#,4},{1,#,2,#,4,3},{1,#,3,2,4},{1,#,4,2,#,#,3},{1,#,4,3,#,2},{2,1,3,#,#,#,4},{2,1,4,#,#,3},{3,1,4,#,2},{3,2,4,1},{4,1,#,#,2,#,3},{4,1,#,#,3,2},{4,2,#,1,3},{4,3,#,1,#,#,2},{4,3,#,2,#,1}]

  也就是少了{3,1,4,#,2},以3為根結點的二叉樹為什么會少了呢?仔細想想,3結點的左孩子可以是1,也可以是2,那么左孩子為1的情況就被忽略了,此時useNode並不等於n,然后就換成左孩子為2結點的情況了。

正確代碼:

/**
 * Definition of TreeNode:
 * class TreeNode {
 * public:
 *     int val;
 *     TreeNode *left, *right;
 *     TreeNode(int val) {
 *         this->val = val;
 *         this->left = this->right = NULL;
 *     }
 * }
 */
class Solution {
public:
    /**
     * @paramn n: An integer
     * @return: A list of root
     */
    vector<TreeNode *> buildT(int ld, int rd){
        vector<TreeNode *> ans;
        if(ld == rd) {
            TreeNode *T = new TreeNode(ld);
            ans.push_back(T);
            return ans;
        }
        if(ld > rd){
            ans.push_back(NULL);
            return ans;
        }
        for(int i=ld; i<=rd; ++i){
            vector<TreeNode *> ansLeft = buildT(ld, i-1);
            vector<TreeNode *> ansRight = buildT(i+1, rd);
            for(auto lx : ansLeft)
                for(auto rx : ansRight){
                    TreeNode *T = new TreeNode(i);
                    T->left = lx;
                    T->right = rx;
                    ans.push_back(T);
                }
        }
        return ans;
    }
    
    vector<TreeNode *> generateTrees(int n) {
        // write your code here
        vector<TreeNode *> ans = buildT(1, n);
        return ans;
    }
};

  分析:在確定當前結點X后,那么X的左孩子結點(或右孩子結點)可能會有多個,那么就把這些可能的結點都存到vector中,然后從左孩子集合中任選出lx結點,以及從右孩子集合中選出rx結點,那么lx和rx就確定了一種形態的二叉樹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM