极大团枚举——Bron-Kerbosh算法

本文深入解析极大团算法,介绍其核心概念如团、极大团和最大团,详细阐述算法思想及剪枝策略,通过代码实现展示算法的具体操作流程,旨在帮助读者理解并掌握极大团算法在图论中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

团(clique): 一个图中的完全子图
极大团(maximal clique): 不被另一个更大的团包含的团
最大团(maximum clique): 含有顶点最多的团
算法思想: 定义两个集合Q和SUBG,Q是一个团包含的的顶点,SUBG是与Q中所有顶点都相邻的顶点集合。将SUBG中任意一个点q加入Q中后,Q是一个更大的团。之后重新计算SUBG(保留SUBG中与q相邻的顶点)。当SUBG为空集时,Q是一个极大团。
剪枝策略:

  1. 将SUBG划分为两个互不相交的子集FINI和CAND。FINI为已经扩展过的顶点集合,CAND为SUBG中的其余顶点。在CAND中选出要加入Q的顶点q之后,不用将FINI与q的相邻部分作为新的SUBG(重新计算后的SUBG是CAND与q相邻的部分)。这样避免了交换顶点顺序导致的同一个团的重复计算。
  2. 在从CAND选出一个q加入Q之前,从SUBG中选出任意一个顶点u,对于CAND中u的相邻顶点nbr(u)可以不再扩展。因为包含nbr(u)的极大团中,必定包含u。为了尽可能减小CAND,我们要选出这样的u,令u与CAND中尽可能多的顶点相邻。

算法的证明和时间复杂度的计算在E. Tomita, A. Tanaka, H. Takahashi, The worst-case time complexity for generating all maximal cliques and computational experiments, Theoretical Computer Science, 2006 中给出

代码如下,时间复杂度为O(3n/3)

#include <bits\stdc++.h>
using namespace std;
vector<list<int>> graph;   //图的邻接链表
bool nbr(int v, int u);   //判断u和v是否相邻
void init();
void expand(int d, int index_subg, int index_cand);
int choose(int d, int index_subg, int index_cand);
int n, m;   //顶点数和边数
int *subg[1010], *cand[1010], *result[1010];   //sbug是候选顶点,cand是需要扩展的顶点,subg-cand是已扩展过的顶点
int clique_count = 0;   //极大团的个数

int main()
{
	init();
	for (int i = 0; i < n; ++i)   //初始时subg和cand包括所有顶点
	{
		subg[0][i] = i;
		cand[0][i] = i;
	}
	expand(0, n, n);
	printf("共有%d个\n", clique_count);
	return 0;
}


bool nbr(int v, int u)   
{
	for (auto i = graph[v].begin(); i != graph[v].end(); ++i)
	{
		if (*i == u)
			return true;
	}
	return false;
}

void init()
{
	FILE* f = fopen("data.txt", "r");
	fscanf(f, "%d %d", &n, &m);  //读入顶点数和边数
	graph.resize(n);
	for (int i = 0; i < 1010; ++i)
	{
		subg[i] = (int*)malloc(n * sizeof(int));
		cand[i] = (int*)malloc(n * sizeof(int));
		result[i] = (int*)malloc(sizeof(int)*n);
	}
	
	for (int i = 0; i < m; ++i)  //创建邻接链表
	{
		int t1, t2;
		fscanf(f, "%d %d", &t1, &t2);
		graph[t1].push_back(t2);
	}
}

void expand(int d, int index_subg, int index_cand)  //d是搜索深度,index_subg是subg集合的元素个数,subg[d][0]~subg[d][index_subg-1]是这一次的subg集合
{
	if (index_subg == 0)
	{
		for (int i = 0; i < d; ++i)
			printf("%d ", result[i][0]);
		++clique_count;
		printf("\n");
	}
	else
	{
		int u = choose(d, index_subg, index_cand);    //选一个顶点u,让cand中u的相邻顶点尽可能多
		int iterate_num = index_cand;
		int offset = 0;
		for (int i = 0; i < iterate_num; ++i)
		{
			int now = cand[d][0+offset];   //当前扩展的点(加入团中的点)
			if (nbr(now, u))   //要扩展的点不包括u的相邻点
			{
				++offset;
				continue;
			}
			result[d][0] = now;   //加入结果集合
			int index_subg_next = 0, index_cand_next = 0;
			for (int j = 0; j < index_subg; ++j)
			{
				if (nbr(subg[d][j], now))   //找出当前subg中的顶点和顶点now相邻的部分
					subg[d + 1][index_subg_next++] = subg[d][j];
			}
			for (int j = 0; j < index_cand; ++j)//找出当前cand中的顶点和顶点now相邻的部分
			{
				if (nbr(cand[d][j], now))
					cand[d + 1][index_cand_next++] = cand[d][j];
			}
			expand(d + 1, index_subg_next, index_cand_next);   //向下一层搜索,subg[d]变成subg[d+1]
			for (int j = 0; j < index_cand - 1; ++j)   //已经用过的点从cand中移除
				cand[d][j] = cand[d][j + 1];
			--index_cand;
		}
	}
}

int choose(int d, int index_subg, int index_cand)
{
	int max = 0, res = cand[d][0];
	for (int i = 0; i < index_subg; ++i)   //在subg中寻找一个点
	{
		int cnt = 0;
		for (int j = 0; j < index_cand; ++j)   //计算点i与cand中的多少个点相邻
		{
			if (nbr(subg[d][i], cand[d][j]))
				++cnt;
		}
		if (cnt > max)
		{
			max = cnt;
			res = cand[d][i];
		}
	}
	return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值