最大团

        在原始部落中,由于食物缺乏,部落居民经常因为争夺猎物发生冲突,几乎每个居民都有自己的仇敌。部落酋长为了组织一支保卫部落的卫队,希望从居民中选出最多的居民加入卫队,并保证卫队中任何两个人都不是仇敌。假设已给定部落中居民间的仇敌关系图,编程计算构建部落护卫队的最佳方案。

        把每个居民编号,作为一个结点,关系友好的两个居民,用边连起来,是仇敌关系的不连线。国王护卫队问题就转化为从图中找出最多的结点,这些结点相互均有连线(任何两个人都不是仇敌)。

        国王护卫队问题属于典型的最大团问题。

        什么是最大团呢?首先来看什么是团。

        完全子图:给定无向图G=(V,E),其中V是结点集,E是边集。G ' =(V ' ,E ' )如果结点集V 'V,E ' E,且G '中任意两个结点有边相连,则称G '是G的完全子图。其实很简单,G '是G的子图,正好G '又是一个完全图,所以称为完全子图。

        团:G的完全子图G '是G的团,当且仅当G ' 不包含在G的更大的完全子图中,也就是说G '是G的极大完全子图。图中c、d是G的团,而a、b不是G的团,因为它们包含在G的更大的完全子图c中。

        最大团:G的最大团是指G的所有团中,含结点数最多的团。d是G的最大团。

        

        根据问题描述可知,将国王护卫队问题转化为从无向图G=(V,E),顶点集是由n个结点组成的集合{1,2,3,…,n},选择一部分结点集V ',即n个结点集合{1,2,3,…,n}的一个子集,这个子集中的任意两个结点在无向图G中都有边相连,且包含结点个数是n个结点集合{1,2,3,…,n}所有同类子集中包含结点个数最多的。

        (1)定义问题的解空间

        问题解的形式为n元组,每一个分量的取值为0或1,即问题的解是一个n元0-1向量。

        解空间:{x1,x2,…,xi,…,xn}

         显约束:xi =0或1 xi =1表示图G中第i个结点在最大团里,xi =0表示图G中第i个结点不在最大团里。 

        (2)解空间的组织结构

        解空间是一棵子集树,树的深度为n,如图所示。

        

        (3)搜索解空间 ·  

         约束条件

        最大团问题的解空间包含2n个子集,这些子集存在集合中的某两个结点没边相连的情况。显然,这种情况下的可能解不是问题的可行解,故需要设置约束条件来判断是否有情况可能导致问题的可行解。 

        第t个结点与前t−1个结点中被选中的结点均有边相连,则放入团里,即x[t]=1;否则,就不能放入团中,即x[t]=0。

        

        ·    限界条件

        前t个结点的状态已确定,当前已放入团内的结点个数cn,剩余节点fn=n−t。如果cn+fn小于或等于当前最优解bestn,则说明不需要再从中间结点z继续向子孙结点搜索。

        因此,限界条件:cn+fn>bestn 

        ·    搜索过程

        国王护卫队问题的搜索和购物车问题的搜索相似,只是进行判断的约束条件和限界条件不同而已。从根结点开始,以深度优先的方式进行。每次搜索到一个结点时,判断约束条件,看是否可以将当前结点加入到护卫队中。如果可以,则沿着当前结点的左分支继续向下搜索;如果不可以加入,判断限界条件,如果满足则沿着当前结点的右分支继续向下搜索,否则回溯。

        

 

        (2)空间复杂度 

        回溯法的另一个重要特性就是在搜索执行的同时产生解空间。在搜过程中的任何时刻,仅保留从开始结点到当前扩展结点的路径,从开始结点起最长的路径为n。程序中我们使用bestp[]数组记录该最长路径作为最优解,所以该算法的空间复杂度为O(n)。

//program 5.3 最大团 
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=105;
int g[maxn][maxn];  //邻接矩阵存储图
bool x[maxn];    //是否将第i个结点加入团中
bool bestx[maxn]; //最优解
int bestn;   //最优值
int cn;   //当前已放入团中的结点数
int n,m;  //结点数,边数

bool place(int t){ //判断是否可以把结点t加入团中
    for(int j=1;j<t;j++){ //结点t与前t-1个结点中被选中的结点是否有相连
        if(x[j]&&g[t][j]==0) //x[j]表示j是被选中的结点,a[t][j]==0表示t和j没有边相连
            return false;
    }
    return 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)){ //满足约束条件,进入左子树,即把结点t加入团中
        x[t]=1;
        cn++;
        backtrack(t+1);
        cn--;
    }
    if(cn+n-t>bestn){ //满足限界条件,进入右子树
        x[t]=0;
        backtrack(t+1);
    }
}

int main() {
	int u,v;//边的两端点 
	int t;//测试用例数 
	cin>>t;
	while(t--){
    	cin>>n>>m;
		memset(g,0,sizeof(g));
		for(int i=1;i<=m;i++){
        	cin>>u>>v;
        	g[u][v]=g[v][u]=1;
    	}
	    cn=0,bestn=0;
	    backtrack(1);
	    cout<<bestn<<endl;
    }
    return 0;
}
/*测试数据 
2
5 8
1 2
1 3
1 4
1 5
2 3
3 4
3 5
4 5
4 5
1 2
1 4
2 3
2 4
3 4
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值