最小支配集(minimal dominating set):对于图G=(V,E)来说,最小支配集指的是从V中取尽量少的点组成一个集合,使得V中剩余的点都与取出来的点有边相连。也就是说,设V'是图G的一个支配集,则对于图中的任意一个顶点u,要么属于集合V',要么与V'中的顶点相连。在V'中除去任何元素后V'不再是支配集,则支配集V'是极小支配集。称G中所有支配集中顶点个数最少的支配集为最小支配集,最小支配集中的顶点个数称为支配数。
最小点覆盖(minimum point coverage):对于图G=(V,E)来说,最小点覆盖指的是从V中取尽量少的点组成一个集合,使得E中所有的边都与取出来的点相连。也就是说设V'是图G的一个顶点覆盖,则对于图中任意一条边(u,v),要么属于u要么属于集合V'。在V'中除去任何元素后V'不再是顶点覆盖,则V'是极小点覆盖。称G中所有顶点覆盖中顶点个数最小的覆盖为最小点覆盖。
最大独立集(minimum independent set):对于图G=(V,E)来说,最大独立集指的是从V中取尽量多的点组成一个集合,使得这些点之间没有边相连。也就是说设V'是图G的一个独立集,则对于图中任意一条边(u,v),u和v不能同时属于集合V',甚至可以u和v都不属于集合V'。在V'中添加任何不属于V'元素后V'不再是独立集,则V'是极大独立集。称G中所有顶点独立集中顶点个数最多的独立集为最大独立集。
对于图G来说,最小支配集,最小点覆盖和最大独立集问题不存在多项式时间的解法。不过如果图G是一棵树,求这三种特殊集合倒是十分容易。目前有两种解法,一种基于贪心思想,另一种基于树形动态规划思想。
1.贪心法求树的最小支配集,最小点覆盖与最大独立集
首先需要注意的是由于默认程序中根节点和其他节点的区别在于根节点的父节点是其自己,所以三种问题对于根节点的处理不同。对于最小支配集和最大独立集,需要检查根节点是否满足贪心条件,但是对于最小点覆盖不可以检查根节点。
时间复杂度分析:该方法是一次深度优先遍历和一次贪心得到最终解,第一步的时间复杂度是O(m),由于这是一颗树,m=n-1。第二步是O(n),一共是O(n)。
最小支配集:贪心策略是首先选择一点为根,按照深度优先遍历得到遍历序列,按照所得序列的反向序列的顺序进行贪心,对于一个既不属于支配集也不与支配集中的点相连的点来说,如果它的父节点不属于支配集,将其父节点加入支配集。
贪心策略中贪心的顺序非常重要,按照深度优先遍历得到遍历序列的反方向进行贪心,可以保证对于每个点来说,当其子树都被处理过后才会轮到该节点的处理,保证了贪心的正确性。
(1).以1号点深度优先搜索整棵树,求出每个点在深度优先遍历序列中的编号和每个点的父节点编号
(2).按照深度优先序列的反向序列检查,如果当前点既不属于支配集也不与支配集中的点相连,且它的父节点不属于支配集,将其父节点加入支配集,支配集中的点的个数加1.标记当前节点,当前节点的父节点和当前节点的父节点的父节点,因为这些节点要么属于支配集(当前点的父节点),要么与支配集中的点相连(当前节点和当前节点的父节点的父节点)。
int p[maxn];
bool select[maxn];
int newpos[maxn];
int now;
int n,m;
void dfs(int x)
{
newpos[now++]=x;
for(int 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]={0};
bool set[maxn]={0};
int ans=0;
for(int 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 main()
{
memset(select,0,sizeof(select));
now=0;
select[1]=true;
p[1]=1;
dfs(1);
return 0;
}
最小点覆盖:贪心策略是如果当前点和当前点的父节点都不属于顶点覆盖集合,则将父节点加入到顶点覆盖集合,并标记当前节点和当前节点的父节点都不属于顶点覆盖集合,则将父节点加入到顶点覆盖集合,并标记当前节点和其父节点都被覆盖。
int greedy()
{
bool s[maxn]={0};
bool set[maxn]={0};
int ans=0;
//不可以检查根节点
for(int 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;
}
最大独立集:贪心策略是如果当前节点没有被覆盖,则将当前节点加入独立集,并标记当前节点和其父节点都被覆盖。
int greedy()
{
bool s[maxn]={0};
bool set[maxn]={0};
int ans=0;
for(int i=n-1;i>=0;i--){
int t=newpos[i];
if(!s[t]){
set[t]=true;
ans++;
s[t]=true;
s[p[t]]=true;
}
}
return ans;
}
2.树形动态规划求树的最小支配集,最小点覆盖和最大独立集
以后更新