贪心法求树的最小支配集,最小点覆盖,最大独立集
定义:
最小支配集:对于图G = (V, E) 来说,最小支配集指的是从 V 中取尽量少的点组成一个集合, 使得 V 中剩余的点都与取出来的点有边相连.也就是说,设 V' 是图的一个支配集,则对于图中的任意一个顶点 u ,要么属于集合 V', 要么与 V' 中的顶点相邻. 在 V' 中除去任何元素后 V' 不再是支配集, 则支配集 V' 是极小支配集.称G 的所有支配集中顶点个数最少的支配集为最小支配集,最小支配集中的顶点个数称为支配数.
最小点覆盖:对于图G = (V, E) 来说,最小点覆盖指的是从 V 中取尽量少的点组成一个集合, 使得 E 中所有边都与取出来的点相连.也就是说设 V' 是图 G 的一个顶点覆盖,则对于图中任意一条边(u, v), 要么 u 属于集合 V', 要么 v 属于集合 V'. 在集合 V' 中除去任何元素后 V' 不再是顶点覆盖, 则 V' 是极小点覆盖. 称 G 的所有顶点覆盖中顶点个数最小的覆盖为最小点覆盖.
最大独立集: 对于图G = (V, E) 来说,最大独立集指的是从 V 中取尽量多的点组成一个集合,使得这些点之间没有边相连.也就是说设 V' 是图 G 的一个独立集,则对于图中任意一条边(u, v), u 和 v 不能同时属于集合 V', 甚至可以 u 和 v 都不属于集合 V'. 在 V' 中添加任何不属于 V' 元素后 V' 不再是独立集,则 V' 是极大独立集.称 G 的所有顶点独立集中顶点个数最多的独立即为最大独立集.
求解:
对于任意图 G 来说,最小支配集, 最小点覆盖, 最大独立集问题是不存在多项式时间的解法,即属于NP问题.但是这里所要求的图 G 是一棵树.可以在多项式的时间内给予解决.在这里利用贪心的思想来解决树上的这三个问题.
(1)最小支配集
贪心策略:首先选择一点为树根,再按照深度优先遍历得到遍历序列,按照所得序列的反向序列的顺序进行贪心,对于一个即不属于支配集也不与支配集中的点相连的点来说,如果他的父节点不属于支配集,将其父节点加入到支配集.
伪代码:
第一步:以根节点深度优先遍历整棵树,求出每个点在深度优先遍历序列中的编号和每个点的父节点编号.
第二步:按照深度优先遍历的反向顺序检查每个点,如果当前点不属于支配集也不与支配集的点相连,且它的父节点不属于支配集,将其父节点加入到支配集,支配集中点的个数加 1, 标记当前节点, 当前节点的父节点, 当前节点的父节点的父节点,因为这些节点要么属于支配集(当前点的父节点),要么与支配集中的点相连(当前节点 和 当前节点的父节点的父节点).
具体实现:
采用链式前向星存储整棵树.整形数组newpos[i] 表示深度优先遍历序列的第 i 个点是哪个点, now 表示当前深度优先遍历序列已经有多少个点了. bool形数组visit[]用于深度优先遍历的判重,整形pre[i]表示点 i 的父节点编号, bool型数组s[i]如果为 true, 表示第 i 个点被覆盖, bool型数组set[i]如果为 true,表示点 i 属于要求的点的集合.
代码:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int maxn = 1000; 5 int pre[maxn];//存储父节点 6 bool visit[maxn];//DFS标记数组 7 int newpos[maxn];//遍历序列 8 int now; 9 int n, m; 10 11 int head[maxn];//链式前向星 12 struct Node {int to; int next;}; 13 Node edge[maxn]; 14 15 void DFS(int x) { 16 newpos[now ++] = x;//记录遍历序列 17 for(int k = head[x]; k != -1; k = edge[k].next) { 18 if(!visit[ edge[k].to ]) { 19 visit[ edge[k].to ] = true; 20 pre[edge[k].to] = x;//记录父节点 21 DFS(edge[k].to); 22 } 23 } 24 } 25 26 int MDS() { 27 bool s[maxn] = {0}; 28 bool set[maxn] = {0}; 29 int ans = 0; 30 for(int i = n - 1; i >= 0; i--) {//逆序进行贪心 31 int t = newpos[i]; 32 if(!s[t]) { //如果当前点没被覆盖 33 if(! set[ pre[t] ]) {//当前点的父节点不属于支配集 34 set[ pre[t] ] = true;//当前点的父节点加入支配集 35 ans ++; //支配集节点个数加 1 36 } 37 s[t] = true; //标记当前点已被覆盖 38 s[ pre[t] ] = true;// 标记当前点的父节点被覆盖 39 s[ pre[ pre[t] ] ] = true;//标记当前点的父节点的父节点被覆盖 40 } 41 } 42 return ans; 43 } 44 45 int main() { 46 /* read Graph message*/ //建图 47 memset(visit, false, sizeof(visit));//初始化 48 now = 0; 49 visit[1] = true; 50 pre[1] = 1; 51 DFS(1);//从根节点开始寻摘遍历序列 52 MDS(); 53 return 0; 54 }
(2)最小点覆盖
贪心策略:同样需要按照反方向的深度优先遍历序列来进行贪心.每检查一个结点,如果当前点和当前点的父节点都不属于顶点覆盖集合,则将父节点加入到顶点覆盖集合,并标记当前节点和其父节点都被覆盖.注意此贪心策略不适用于根节点,所以要把根节点排除在外.
具体实现:实现上基本和求最小支配集差不多,仅仅需要改动贪心部分即可.
代码:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int maxn = 1000; 5 int pre[maxn];//存储父节点 6 bool visit[maxn];//DFS标记数组 7 int newpos[maxn];//遍历序列 8 int now; 9 int n, m; 10 11 int head[maxn];//链式前向星 12 struct Node {int to; int next;}; 13 Node edge[maxn]; 14 15 void DFS(int x) { 16 newpos[now ++] = x;//记录遍历序列 17 for(int k = head[x]; k != -1; k = edge[k].next) { 18 if(!visit[ edge[k].to ]) { 19 visit[ edge[k].to ] = true; 20 pre[edge[k].to] = x;//记录父节点 21 DFS(edge[k].to); 22 } 23 } 24 } 25 26 int MVC() { 27 bool s[maxn] = {0}; 28 bool set[maxn] = {0}; 29 int ans = 0; 30 for(int i = n - 1; i >= 1; i--) {//逆序进行贪心,排除掉其根节点 31 int t = newpos[i]; 32 if(!s[t] && !s[ pre[t] ]) {//如果当前节点和其父节点都不属于顶点覆盖集合 33 set[ pre[t] ] = true;//把其父节点加入到顶点覆盖集合 34 ans ++; //集合内顶点个数加 1 35 s[t] = true;//标记当前节点被覆盖 36 s[ pre[t] ] = true;//标记其父节点被覆盖 37 } 38 } 39 return ans; 40 } 41 42 int main() { 43 /* read Graph message*/ //建图 44 memset(visit, false, sizeof(visit));//初始化 45 now = 0; 46 visit[1] = true; 47 pre[1] = 1; 48 DFS(1);//从第一个根节点开始寻找遍历序列 49 MDS(); 50 return 0; 51 }
(3)最大独立集
贪心策略:同样和以上两个贪心问题的贪心方法差不多,需要反向遍历DFS的遍历序列,检查每一个点,如果当前节点没有被覆盖,则将当前节点加入独立集,并标记当前点和其父节点都被覆盖.
代码:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int maxn = 1000; 5 int pre[maxn];//存储父节点 6 bool visit[maxn];//DFS标记数组 7 int newpos[maxn];//遍历序列 8 int now; 9 int n, m; 10 11 int head[maxn];//链式前向星 12 struct Node {int to; int next;}; 13 Node edge[maxn]; 14 15 void DFS(int x) { 16 newpos[now ++] = x;//记录遍历序列 17 for(int k = head[x]; k != -1; k = edge[k].next) { 18 if(!visit[ edge[k].to ]) { 19 visit[ edge[k].to ] = true; 20 pre[edge[k].to] = x;//记录父节点 21 DFS(edge[k].to); 22 } 23 } 24 } 25 26 int MIS() { 27 bool s[maxn] = {0}; 28 bool set[maxn] = {0}; 29 int ans = 0; 30 for(int i = n - 1; i >= 0; i--) {//按照DFS遍历序列的逆序进行贪心 31 int t = newpos[i]; 32 if(!s[t]) {//如果当前节点没有被覆盖 33 set[t] = true;//把当前节点加入到独立集 34 ans ++;//独立集中点的个数加 1 35 s[t] = true;//标记当前点已经被覆盖 36 s[ pre[t] ] = true;//标记当前点的父节点已经被覆盖 37 } 38 } 39 return ans; 40 } 41 42 int main() { 43 /* read Graph message*/ //建图 44 memset(visit, false, sizeof(visit));//初始化 45 now = 0; 46 visit[1] = true; 47 pre[1] = 1; 48 DFS(1);//从第一个根节点开始寻找遍历序列 49 MDS(); 50 return 0; 51 }
参考书籍<<图论及应用>>以及网上的其他资料.