获取树的最小顶点覆盖的好算法是什么?

时间:2009-05-29 16:13:50

标签: algorithm tree dynamic-programming

获取树的最小顶点覆盖的好算法是什么?

INPUT:

节点的邻居。

输出:

最小顶点数。

7 个答案:

答案 0 :(得分:12)

我在阅读完这些答案后并不完全明白,所以我想我会从here发布一个

一般的想法是,您将树根植于任意节点,并询问该根是否在封面中。如果是,则通过递归计算以其子项为根的子树的最小顶点覆盖。如果不是,那么根的每个子节点都必须位于顶点覆盖中,以便覆盖根与其子节点之间的每个边缘。在这种情况下,你递归根的孙子。

例如,如果您有以下树:

    A
   / \
  B   C
 / \ / \
D  E F  G

请注意,通过检查,您知道最小顶点覆盖为{B, C}。我们会找到这个最小的封面。

这里我们从A开始。

A在封面

我们向下移动到BC的两个子树,并对此算法进行说明。我们不能简单地声明BC不在封面中,因为即使涵盖了ABAC,我们也无法说明我们是否是否需要BC

(想想下面的树,其根和它的一个孩子都在最小封面({A, D}

  A
 /|\___
B C    D
      /|\
     E F G

A不在封面

但我们知道必须涵盖ABAC,因此我们必须在封面上添加BC。由于BC属于封面,我们可以对其子女进行递归,而不是在BC上递归(即使我们这样做了,也不会给我们更多信息)。

“正式”

C(x)x的最小封面大小。

然后,

C(x) = min ( 
            1 + sum ( C(i) for i in x's children ),                    // root in cover
            len(x's children) + sum( C(i) for i in x's grandchildren)  // root not in cover
            )

答案 1 :(得分:11)

T(V,E)是一棵树,这意味着对于任何叶子,任何最小顶点覆盖必须包括叶子或与叶子相邻的顶点。这为我们提供了以下算法来寻找S,即顶点覆盖:

  1. 在树中查找树的所有叶子(BFS或DFS),O(| V |)。
  2. 如果(u,v)是边缘,使得v是叶子,则将u添加到顶点覆盖,并修剪(u,v)。这将为您留下森林T_1(V_1,E_1),...,T_n(U_n,V_n)。
  3. 现在,如果V_i = {v},意味着| V_i | = 1,则可以删除该树,因为覆盖了v上的所有边。这意味着我们有一个递归的终止条件,我们有一个顶点或没有顶点,我们可以计算 S_i 作为每个 T_i 的封面,并定义S因为步骤2中的所有顶点都与每个 T_i 的封面结合在一起。
  4. 现在,剩下的就是验证如果原始树只有一个顶点,我们返回1并且永远不会开始递归,并且可以计算最小顶点覆盖。

    修改

    实际上,在稍微考虑之后,可以使用简单的DFS变体来完成。

答案 2 :(得分:10)

我希望here您可以找到更多相关问题的答案。


我在想我的解决方案,可能你需要对它进行改进,但只要动态编程在你的一个标签中,你可能需要:

  1. 对于每个u顶点定义S +(u)是 顶点u和S-(u)的覆盖大小 封面没有顶点u。
  2. S +(u)= 1 + Sum(S-(v))对于你的每个孩子v。
  3. S-(u)=每个孩子v的总和(最大{S-(v),S +(v)})。
  4. 答案是最大值(S +(r),S-(r)),其中r是树的根。

  5. 阅读this后。更改了上面的算法以找到最大的独立集,因为在wiki文章中说明了

      

    当且仅当其补码是顶点覆盖时,该集是独立的。

    因此,通过将min更改为max,我们可以找到最大的独立集,并通过补充最小顶点覆盖,因为两个问题都是等价的。

答案 3 :(得分:2)

{- Haskell implementation of Artem's algorithm -}

data Tree = Branch [Tree]
    deriving Show

{- first int is the min cover; second int is the min cover that includes the root -}
minVC :: Tree -> (Int, Int)
minVC (Branch subtrees) = let
    costs = map minVC subtrees
    minWithRoot = 1 + sum (map fst costs) in
    (min minWithRoot (sum (map snd costs)), minWithRoot)

答案 4 :(得分:2)

我们可以使用基于DFS的算法来解决这个问题:

DFS(node x)
{

    discovered[x] = true;

    /* Scan the Adjacency list for the node x*/
    while((y = getNextAdj() != NULL)
    {

        if(discovered[y] == false)
        {

            DFS(y);

           /* y is the child of node x*/
           /* If child is not selected as a vertex for minimum selected cover
            then select the parent */ 
           if(y->isSelected == false)
           {
               x->isSelected = true;
           }
        }
   }
}

永远不会为顶点覆盖选择叶节点。

答案 5 :(得分:1)

我们需要找到我们必须做出的每个节点的最小顶点覆盖,要么包括它,要么不包括它。但是根据每个边缘(u,v)的问题,'u'或'v'中的任何一个都应该在封面中,所以我们需要注意如果当前顶点不包括在内,那么我们应该包括它的子节点,如果我们当前包含当前顶点,我们可能会也可能不会根据最优解来包含它的子节点。

这里,DP1 [v]代表任何顶点v =当我们包含它时。 DP2 [v]代表任何顶点v =当我们不包含它时。

DP1 [v] = 1 + sum(min(DP2 [c],DP1 [c])) - 这意味着包括当前,并且可能包括或不包括其子项,基于最佳值。

DP2 [v] = sum(DP1 [c]) - 这意味着不包括当前我们需要包含当前顶点的子节点。这里,c是顶点v的孩子。

然后,我们的解决方案是min(DP1 [root],DP2 [root])

#include <bits/stdc++.h>
using namespace std;

vector<vector<int> > g;

int dp1[100010], dp2[100010];

void dfs(int curr, int p){
    for(auto it : g[curr]){
        if(it == p){
            continue;
        }
        dfs(it, curr);
        dp1[curr] += min(dp1[it], dp2[it]);
        dp2[curr] += dp1[it];
    }
    dp1[curr] += 1;
}

int main(){
    int n;
    cin >> n;
    g.resize(n+1);
    for(int i=0 ; i<n-1 ; i++){
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);
    cout << min(dp1[1], dp2[1]);
    return 0;
} 

答案 6 :(得分:0)

我只需使用线性程序来解决最小顶点覆盖问题。 作为整数线性程序的公式可能类似于此处给出的公式:ILP formulation

我不认为您自己的实现会比这些高度优化的LP解算器更快。