回溯、图论——最大团问题(求最大完全子图)
1.问题分析要想解决最大团问题,也就是求最大完全子图。我们需要了解相关概念,现在有如下图:(1)完全子图:给定无向图G=(V,E),其中V是顶点集,E是边集。G'=(V',E')如果顶点集V'∈V,E'∈E,且G'种任意两个点有边相连,则称G'是G的完全子图。例如下面的几个图都是上图的完全子图:(2)团:G的完全子图是G的团,当且仅当G'不包含在G的更大的完全子图...
1.问题分析
要想解决最大团问题,也就是求最大完全子图。我们需要了解相关概念,现在有如下图:
(1)完全子图:
给定无向图G=(V,E),其中V是顶点集,E是边集。G'=(V',E')如果顶点集V'∈V,E'∈E,且G'种任意两个点有边相连,则称G'是G的完全子图。例如下面的几个图都是上图的完全子图:
(2)团:
G的完全子图是G的团,当且仅当G'不包含在G的更大的完全子图中,也就是说G'是G的极大完全子图。比如图中(c),(d)是G的团,而(a)(b)不是G的团,因为他们包含在G的更大的完全子图(c)中。
(3)最大团:
G的最大团是指G的所有团中,含顶点数最大的团,比如说(d)就是最大团。
2.算法设计
(1)约束条件:
最大团问题的解空间包含2^n个子集,这些子集存在集合中的某两个结点没边相连的情况。显然这种情况的可能姐不是问题的可行解,故需要设置约束条件来判断是否有情况可能导致问题的可行解。
假设现在扩展结点到t层,那么1~t-1个结点状态(是否已经在团里)已经确定。接下来沿着扩展点左分支进行扩展,此时需要判断是否将 t结点 放进团里。则判断 t结点 和1~t-1 个结点中被选中的结点是否有相连,相连就放进团里,x[t]=1,否则不放进团里,x[t]=0.如图所示:
(2)限界条件(剪枝函数):
假设当前的扩展结点为z,如果z处于第t层,前面1~t-1 层的结点的状态已经确定了。接下来确定t结点的状态。前t个结点状态确定当前已放团内的结点个数(用cn表示),假设t+1 ~ n 的所有结点都放进团,用fn表示,且fn=n-t ,则cn+fn是所有从根出发的路径中经过中间结点z的可行解所包含结点个数的上界。
如果fn+cn小于等于当前最优解bestn,则说明不再需要从中间结点z继续像子孙结点搜索。因此,限界条件可以描述为:cn+fn>bestn。
(3)搜索过程:
从根节点开始,以深度游戏按的方式进行。每次搜索到一个结点,判断约束条件,看是否可以将当前结点加入到团中。如果可以,则沿着当前结点左分支继续向下搜索,如果不可以,判断戒指函数,如果满足则沿着当前结点的右分支继续向下搜索。
3.源代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> using namespace std; const int N=111; //设置矩阵的大小 int graph[N][N]; //用邻接矩阵存储图 bool x[N]; //是否将i个顶点加入团中 bool bestx[N]; //记录最优已经放入团中的顶点 int bestn; //记录最优值 int cn; //已经放入团中的结点数 int n;//顶点个数 int m; //边的个数 bool place(int t) //判断是否能放进团中 { bool OK =true; for (int j=1;j<t;j++) //判断目前扩展的t顶点和前面t-1个顶点是否相连。 { if(x[j] && graph[t][j]==0) //如果不相连 { OK=false; //返回false break; } } return OK; //如果相连。返回true } void backtrack(int t) //回溯,递推 { if(t>n) //当到达叶子结点 { for(int i=1;i<=n;i++) { bestx[i]=x[i]; //记录最优值结点号 } bestn=cn; //记录最优值 return; } if(place(t)) //如果能放进团中 { x[t]=1;//标为1 cn++; //个数+1 backtrack(t+1); //向下递推 cn--; //向上回溯 } if(cn+n-t>bestn) //限界条件,进入右子树,不能加入团中。 { x[t]=0; //不能放入团中,标为0 backtrack(t+1); //向下递推。 } } int main() { int u; //结点1 int v; //结点2 cout << "请输入结点的个数n;"<< endl; cin >> n; cout << "请输入边数m:"<< endl; cin >>m; memset(graph,0,sizeof(graph)); cout <<"请输入有边相连的两个顶点u和v:"<< endl; for (int i=1;i<=m;i++) { cin >> u>> v; graph[u][v]=1; graph[v][u]=1; } bestn=0; cn=0; backtrack(1); cout << "最大团的结点个数有:"<< bestn << endl; cout << "结点有:"<< endl; for (int i=1;i<=n;i++) { if(bestx[i]) { cout << i << " "; //输出的是结点号 } } return 0; }
4.测试结果
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)