Bron-kerbosch算法-求图的最大团,极大团

概念介绍: 

团:每个顶点都两两相连
极大团:没有被包含在其他团中的团
最大团:顶点数最多的极大团
独立集:从图中取k个点,任意两点之间不连接
最大团顶点数量:等于补图中最大独立集顶点数
补图:在图论里面,一个图G的补图(complement)或者反面(inverse)是一个图有着跟G相同的点,而且这些点之间有相连当且仅当在G里面他们没有边相连。在制作图的时候,你可以先建立一个有G所有点的完全图,然后清除G里面已经有的边来得到补图。这里的补图并不是图本身的补集;因为只有边的部份合乎补集的概念。

算法介绍:

 算法伪代码:
          R集合中保存已经加入当前极大团的点,p集合中保存有可能加入极大团的点,x集合保存已经完成极大团计数的点                (作     用是     判重)
          初始化R,X为空集,P中为所有顶点
           dfs(R,P,X)
                 if(p和x均为空) 输出R集合为一个极大团
                 for 从P中选取一个点a,与a相连的点集为Q(a)
                          dfs(R并上a,P和Q(a)的交集,X和Q(a)的交集)
                                   从P中移除a点
                                   把a点加入X集合


算法解析:基础的Born_Kerbosch算法,对于每一个点P中的点v我们把v加入集合R,对在P集合中且与点v相连的这部分集合中寻找下一个可能加入R集合的点,回溯时我们把v从P集合中移出,加入X集合代表当前状态下对包含点v的极大团已经计算完毕了。R集合为极大团的时候,必须要满足P与X都是空的,P存放的是还可能加入R集合的点,P集合为空代表没有点还能再加入到R集合当中,而X集合存放的是已经完成极大团计数的点,而且X集合中的点必然是与所有R集合中的点都有边的(因为我们每次向下进行dfs的时候,还对P集合和X集合分别进行取与R集合内都相邻的操作来保证),也即X集合中点必然可以与R集合构成极大团,如果X集合不是空的的话,那么说明R集合中的极大团是在之前计算包含X集合中的点的极大团的时候已经计算过了的,故当且仅当P、X都为空集合的时候R才是一个极大团。

算法优化:我们知道在上述的算法中必然有许多重复计算之前计算过的极大团然后回溯的过程。我们考虑如下问题,取集合P∪X中的一个点u,要与R集合构成极大团,那么取的点必然是P∩N(u)中一个点(N(u)代表与u相邻的点)。通俗的讲就是如果取完u之后我们再取与u相邻的点v也能加入到极大团,那么我们只取u就好了,从而剪掉了之后对v的白用功,所以再要么就是取与u不相邻的点,这样我们照样可以重复不漏的计算所有极大团,从而减少许多不必要的计算。而我们要想进一步减少计算,我们就可以取邻居尽可能多的u,即使我们要遍历的点尽可能减少,但是其实没必要写,寻找合适的u也会减少一定的效率。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<set>
#include<stack>
#include<map>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn = 130;
bool mp[maxn][maxn];
int some[maxn][maxn], none[maxn][maxn], all[maxn][maxn];
int n, m, ans;
void dfs(int d, int an, int sn, int nn)
{
	if(!sn && !nn) {++ans;for(int i=0;i<an;i++)cout<<all[d][i]<<" ";cout<<endl;}
	int u = some[d][0];
	for(int i = 0; i < sn; ++i)
	{
		int v = some[d][i];
		if(mp[u][v]) continue;//和u连接的点停止搜索,u和其自身
		for(int j = 0; j < an; ++j)
		all[d+1][j] = all[d][j];
		all[d+1][an] = v;
		int tsn = 0, tnn = 0;
		for(int j = 0; j < sn; ++j)
		if(mp[v][some[d][j]])
		some[d+1][tsn++] = some[d][j];
		for(int j = 0; j < nn; ++j)
		if(mp[v][none[d][j]])
		none[d+1][tnn++] = none[d][j];
		dfs(d+1, an+1, tsn, tnn);
		some[d][i] = 0, none[d][nn++] = v;
		if(ans > 1000) return;
	}
}
int work()
{
	ans = 0;
	for(int i = 0; i < n; ++i) some[1][i] = i+1;
	dfs(1, 0, n, 0);
	return ans;
}
int main()
{
	while(~scanf("%d %d", &n, &m))
	{
		memset(mp, 0, sizeof mp);
		for(int i = 1; i <= m; ++i)
		{
			int u, v;
			scanf("%d %d", &u, &v);
			mp[u][v] = mp[v][u] = 1;
		}
		int tmp = work();
		if(tmp > 1000) puts("Too many maximal sets of friends.");
		else printf("%d\n", tmp);
	}
	return 0;
}
/*测试样例
5 6
1 2
4 5
3 1
3 2
3 4
3 5
*/

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值