待字闺中是一个不错的面试题集。
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) }
根据上面这条公式,我们可以使用递归的方法来实现。原理很简单,就是通过统计它的孩子节点和孙子节点的个数,然后得到其中的最大值返回,代码实例如下所示:
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中呢?
解法如下:
-
dp[0,1][x]表示以节点x为根的子树不取或取x的结果,第一维取0,表示x不在LIS中,第一维取1,表示x在LIS中;
-
dp[0][leaf]=0,dp[1][leaf]=value of the leaf
-
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.
-
最后取max(dp[0][root],dp[1][root])
这里比较有意思的是第一维来表示第二维的节点,作为根节点,是否在LIS中。上面的过程在,前序或者后序的基础之上进行都可以,原则就是一点,有儿子的,就先计算完儿子,再计算父节点。