树的最小支配集 最小点覆盖 与 最大独立集 (图论)
首先看一下三者的定义:
定义1 对于图G=(V,E)来说,最小支配集指的是从V中取尽量少的点组成一个集合,使得对于V中剩余的点都与取出来的点有边相连。也就是说,设V‘是图G的一个支配集,则对于图中的任意一个顶点u,要么属于集合V’,要么与V‘中的顶点相邻。在V’中出去任何元素后V‘不再是支配集,则支配集是极小支配集。称G的所有支配集中顶点个数最少的支配集为最小支配集,最小支配集中顶点的个数称为支配数。
定义2 对于图G=(V,E)来说,最小点覆盖指的是从V中取尽量少的点组成一个集合,使得E中所有的边都与取出来的点相连。也就是说,设V‘是图G的一个顶点覆盖,则对于图中的任意一条边(u,v),要么u属于集合V’,要么v属于集合V‘。在V‘中除去任何元素后V’不在是顶点覆盖,则V‘是极小顶点覆盖。称G的所有顶点覆盖中顶点个数最少的覆盖为最小点覆盖。
定义3 对于图G=(V,E)来说,最大独立集指的是从V中取尽量多的点组成一个集合,使得这些点之间没有边相连。也就是说,设V’是图G的一个独立集,则对于图中任意一条边(u,v),u和v不能同时属于集合V',甚至可以u和v都不属于集合V‘。在V’中添加任何不属于V‘元素后V’不再是独立集,则V‘是极大独立集。称G的所有顶点独立集中顶点个数最多的独立集为最大独立集。
对于任意图G来说,这三个问题不存在多项式时间的解法。不过对于树来说,却很容易。目前有两种解法,一种基于贪心思想,另一种基于树形动态规划思想。
一:贪心法
基本算法:
以最小支配集为例,对于树上的最小支配集问题,贪心策略是首先选择一点为根,按照深度优先遍历得到遍历序列,按照所得序列的反向序列的顺序进行贪心,对于一个既不属于支配集也不与支配集中的点相连的点来说,如果他的父节点不属于支配集,将其父节点加入支配集。
这里注意到贪心的策略中贪心的顺序非常重要,按照深度优先遍历得到遍历序列的反向进行贪心,可以保证对于每个点来说,当期子树都被处理过后才轮到该节点的处理,保证了贪心的正确性。
①以1号点深度优先搜索整棵树,求出每个点在深度优先遍历序列中的编号和每个点的父节点编号。
②按照深度优先序列的反向序列检查,如果当前点既不属于支配集也不与支配集中的点相连,且他的父节点不属于支配集,将其父节点加入支配集,支配集中点的个数加1.标记当前节点、当前节点的父节点和当前节点的父节点的父节点,因为这些节点要么属于支配集,要么与支配集中的点相连。
最小点覆盖于最大独立集与上面的做法相似。对于最小点覆盖来说,贪心的策略是,如果当前点和当前点的父节点都不属于顶点覆盖集合,则将父节点加入到顶点覆盖集合,并标记当前节点和其父节点都被覆盖。对于最大独立集来说,贪心策略是如果当前点没有被覆盖,则将当前节点加入到独立集,并标记当前节点和其父节点都被覆盖。
需要注意的是由于默认程序中根节点和其他节点的区别在于根节点的父节点是其自己,所以三种问题对根节点的处理不同。对于最小支配集和最大独立集,需要检查根节点是否满足贪心条件,但是对于最小点覆盖不可以检查根节点。
具体实现:
采用链式前向星存储整棵树。对于DFS(),newpos[i]表示深度优先遍历序列的第i个点是哪个点,now表示当前深度优先遍历序列已经有多少个点了。select[]用于深度优先遍历序列的判重,p[i]表示点i的父节点的编号。对于greedy(),s[i]如果为true,表示第i个点被覆盖。set[i]表示点i属于要求的点集。
首先是深度优先遍历,得到遍历序列。代码如下:
int p[maxn]; bool select[maxn]; int newpos[maxn]; int now; int n,m; void DFS(int x) { newpos[now++]=x; int k; for(k=head[x];k!=-1;k=edge[k].next) { if(!select[edge[k].to]) { select[edge[k].to]=true; p[edge[k].to]=x; DFS(edge[k].to); } } }
对于最小支配集,贪心函数如下:
int greedy() { bool s[maxn]; bool set[maxn]={0}; int ans=0; int i; for(i=n-1;i>=0;i--) { int t=newpos[i]; if(!s[t]) { if(!set[p[t]]) { set[p[t]]=true; ans++; } s[t]=true; s[p[t]]=true; s[p[p[t]]]=true; } } return ans; }
对于最小点覆盖,贪心函数如下:
int greedy() { bool s[maxn]={0}; bool set[maxn]={0}; int ans=0; int i; for(i=n-1;i>=1;i--) { int t=newpos[i]; if(!s[t]&&s[p[t]]) { set[p[t]]=true; ans++; s[t]=true; s[p[t]]=true; } } return ans; }
对于最大独立集,贪心函数如下: