最大团问题.docx
- 文档编号:7221178
- 上传时间:2023-01-22
- 格式:DOCX
- 页数:6
- 大小:17.40KB
最大团问题.docx
《最大团问题.docx》由会员分享,可在线阅读,更多相关《最大团问题.docx(6页珍藏版)》请在冰豆网上搜索。
最大团问题
这是一个很典型的NP问题.很长一段时间一直在想如何解决它,直到那天看了一位前人推荐的文档,得到一些启发才顺利的解决了这个困扰我多天的问题.
典型描述:
给定一个图G,要求G的最大团(团是指G的一个完全子图,该子图不包含在任何其他的完全子图当中。
最大团指其中包含顶点最多的团).
该命题可以有很多变种,例如1002,1654的放机器人,实质上都是求最大团的问题,当然,由于问题的特殊性,他们或许还可以用其他更高效的算法来解.毕竟,问题抽象,解法一般后其实现难度和复杂度也会增大.
解决该问题的一般算法该是搜索.设其包含顶点a1,a2,a3,a4,a5·····an。
从a1开始搜索,依次搜索所有与其相邻的点······典型的指数时间搜索。
那天看到一篇文章,专门论述了这个问题。
它不是采用我们惯用的从a1开始搜索的方法,而是反了过来,从an开始走,当然,走的时候还是只向前走(a5开始,就只搜a6,a7······),这样做有什么好处呢?
实际上类似于动态规划,这样我们每做的一次搜索都可以为后面的搜索所用,而我们先前的搜索方法则基本上每一次搜索都会从头开始去寻找最有解。
并且,我们注意到这末一个结论:
如果max【i】表示搜索a1得到的解,则max【i-1】=max【i】+1或者max【i】。
这个结论是很明显的。
如果ai~an的点可能形成的最大完全图含Max【i】个接点,则a(i-1)~an能形成的完全子图最多比前一个多1个顶点。
这些给我们的搜索提供了一个很强的约束条件。
这里提供ZJU1492的解答源代码加以具体说明(TLE2次,AC一次1.61s):
#include
#include
intjoint[50][50];
intSize;
intMAX;
intDP[50];
boolfind;
intreachEnd(intstart,intsets[])//返回-1表示邻接顶点集已经为空集,否则返回第一个公共顶点。
{
intlp;
for(lp=start;lp if(sets[lp]) returnlp; return-1; } //Visit[]表示邻接数组,start表示搜索起点,切记只可向大的方向搜。 Depth表示搜索深度。 可以看到我们在函数中又定义了2 个和Visit【】一样内容的数组,这主要考虑到数组传递的指针,直接用Visit【】会直接改变joint邻接数组的内容,搜索之后状态无法还原。 而之所以是两个,因为SET要保存信息以便下次回溯。 Sets则是用来进行回溯时传递参数。 voidDFS(intVisit[],intstart,intdepth) { intloop; intfirst; intsets[50],SET[50]; memcpy(sets,Visit,Size*4); memcpy(SET,Visit,Size*4); if((first=reachEnd(start,sets))==-1) { if(depth>MAX){ MAX=depth; find=true; } return; } while(first! =-1){ if(depth+Size-start<=MAX)//不可能找到最有解 return; if(depth+DP[first]<=MAX)//不可能找到最有解 return; sets[first]=0; SET[first]=0;//从邻接顶点集中清除first顶点。 for(loop=first+1;loop if(SET[loop]==1&&joint[first][loop]==1) sets[loop]=1; else sets[loop]=0; DFS(sets,first,depth+1); if(find) return;//很巧妙的地方因为找到的只可能比原来的多一个结点,所以说,如果我找到了那就一定是当前最大的了 first=reachEnd(first,SET);//更新接点 } } intmain() { intloop,lp; intVisit[50]; while(scanf("%d",&Size)! =EOF&&Size! =0) { for(loop=0;loop for(lp=0;lp scanf("%d",joint[loop]+lp); MAX=0; for(loop=Size-1;loop>=0;loop--){ find=false; memcpy(Visit,joint[loop],Size*4); DFS(Visit,loop,1); DP[loop]=MAX; } printf("%d\n",DP[0]); } return0; } 图我们仍然用邻接数组表示。 最大的不同是DFS部分,可以看到,我们没有用普通的DFS一次就完成整个搜索的策略,而是对an,a(n-1),······a2,a1分开调用DFS,这样方便我们记录每次搜索的结果。 看到DFS部分,reachEnd函数用来判定公共顶点集是否为空集,是空集,表明已经达到解状态树的叶接点,可以记录结果了。 Find表示此次搜索已经得到一个优化解,可以返回,结束这次的搜索了。 否则,继续搜索。 DFS部分的解释参见程序部分。 到这里,这道题就大部分解决了。 我们注意到这种算法的要点: 从小到大,小处的结论用来给更大范围的搜索提供强剪枝条件。 这和我们递归的至顶而下的做法可谓背道而驰。 这种算法将递归和小处规划紧密结合,让我们看到算法的精妙之处。 Ps: 我手中有PDF版的该算法的原版文章,有兴趣的给我发message。 ZJU1002和ZJU1654本质上属于这类问题,但是由于那种矩阵转化为邻接表比较麻烦,所以不推荐用该解法。 另外注 图G的最大独立集=G的补图(G')的最大团值 另外: 图G的最小顶点覆盖=顶点数(V)-G的补图(G')的最大团值 所以,对于第四个内容,归根结底都是求最大团。 依样画葫芦呵呵 #include #include #defineN56 intmap[N][N]; intdp[N]; intvisit[N]; intMax=0; intfinded; intn; intfind(intstart,intflag[]) { inti; for(i=start;i if(flag[i]) returni; return-1; } voiddfs(intstart,intvisit[],intdepth) { inti; intset[N]; intflag[N]; memcpy(set,visit,4*n); memcpy(flag,visit,4*n); intfirst; first=find(start,flag); if(first==-1) { if(depth>Max) { Max=depth; finded=1; } return; } while(first! =-1) { if(depth+n-start<=Max)return; if(depth+dp[first]<=Max)return; set[first]=0; flag[first]=0; for(i=first+1;i { if(flag[i]&&map[first][i]) set[i]=1; elseset[i]=0; } dfs(first,set,depth+1); if(finded) return; first=find(first,flag); } } intmain() { while(scanf("%d",&n),n) { inti,j; for(i=0;i { for(j=0;j scanf("%d",&map[i][j]); dp[i]=0; } Max=0; for(i=n-1;i>=0;--i) { finded=0; memcpy(visit,map[i],4*n); dfs(i,visit,1); dp[i]=Max; } printf("%d\n",dp[0]); } return0; }
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 大团 问题