待字闺中之LISS问题

来源:互联网 发布:淘宝店铺主营怎么修改 编辑:程序博客网 时间:2024/04/29 07:22

待字闺中是一个不错的面试题集。


Largest Independent Set

含义如下:
给定一颗二叉树,找到满足如下条件的最大节点集合:集合中的任意两个结点之间,都没有边。如下图所示


      10
     /  \
    20   30
   /  \    \
  40  50    60
      / \
70  80


LIS大小为5,集合为{10,40,60,70,80}




就是给你一颗二叉树,任意两个结点都没有相同边的集合。


通常来讲,树的问题一般都是可以通过递归来解决的。递归是自顶向下的分析问题分析
原问题是否能够分解为子问题。
我们先从LIS集合大小入手,设f(x)为以x为根的数的LIS的大小,根据题目的定义我们可以知道:


当x不在LIS中时,f(x)=sum(所有儿子节点的f(儿子))


当x在LIS中的时候,则x的儿子节点肯定不在LIS中,考虑孙子节点,
则f(x)=sum(所有孙子节点的f(孙子)) + 1,后面的1是x本身。


1) Optimal Substructure: 
Let LISS(X) indicates size of largest independent set of a tree with root X.


LISS(X) = MAX { (1 + sum of LISS for all grandchildren of X),
                     (sum of LISS for all children of X) }


If a node is considered as part of LIS, then its children cannot be part of LIS, 
but its grandchildren can be. Following is optimal substructure property.


这种二叉树的问题,都是用递归的方法来实现了。


LISS(X) = MAX { (1 + sum of LISS for all grandchildren of X),
                     (sum of LISS for all children of X) }

根据上面这条公式,我们可以使用递归的方法来实现。原理很简单,就是通过统计它的孩子节点和孙子节点的个数,然后得到其中的最大值返回,代码实例如下所示:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct TreeNode
{
    int data;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int data): data(data), left(NULL), right(NULL)
    {}
};

int LISS(TreeNode *root)
{
    if(root == NULL)
    {
        return 0;
    }

    int childsize = LISS(root->left) + LISS(root->right);


    int leftsize = 0;
    int rightsize = 0;
    if(root->left != NULL)
        leftsize = LISS(root->left->left) + LISS(root->left->right);
    if(root->right != NULL)
        rightsize = LISS(root->right->left) + LISS(root->right->right);


    return max(leftsize + rightsize + 1, childsize);

}



int main()
{

    TreeNode *root         = new TreeNode(20);
    root->left                = new TreeNode(8);
    root->left->left          = new TreeNode(4);
    root->left->right         = new TreeNode(12);
    root->left->right->left   = new TreeNode(10);
    root->left->right->right  = new TreeNode(14);
    root->right               = new TreeNode(22);
    root->right->right        = new TreeNode(25);
    cout << LISS(root);
    system("pause");

}


      有没有更优的方法呢?

上面的递归过程中,子问题重复的比较多。最明显的就是,x的儿子节点x的父节点的孙子节点,几乎都要重复计算,所以改进空间很大。改进的方法,最直接的就是采用缓存将计算过的子问题,缓存起来,待后面直接使用,很简单,却又是非常实用的。

那么动态规划如何解呢?动态规划是自底向上解决问题,对于上面的递归过程,如何表示x是否在LIS中呢?

解法如下:


  1. dp[0,1][x]表示以节点x为根的子树不取或取x的结果,第一维取0,表示x不在LIS中,第一维取1,表示x在LIS中;

  2. dp[0][leaf]=0,dp[1][leaf]=value of the leaf

  3. dp[0][x]=max of max dp[t=0,1][y is son of x], dp[1][x]=sum of dp[0][y is son of x] + value of x.

  4. 最后取max(dp[0][root],dp[1][root])


这里比较有意思的是第一维来表示第二维的节点,作为根节点,是否在LIS中。上面的过程在,前序或者后序的基础之上进行都可以,原则就是一点,有儿子的,就先计算完儿子,再计算父节点。




0 0