最大团问题

最大团问题(Maximum Clique Problem, MCP)是图论中一个经典的组合优化问题,也是一类NP完全问题。看到前面的描述可能会觉得很复杂,但是最大团的定义其实很简单,通俗点讲最大团问题就是一个最大的完全子图的问题。
因为是NP完全问题,自然没有多项式时间复杂度求解的算法,解决这个问题有很多有效的算法:遗传算法、模拟退火算法、禁忌算法等等,但是这些算法比较高级,这里讲一种比较简单的搜索算法。

算法

概述

总体上来讲,这个算法就是通过DFS进行搜索,通过剪枝来优化的方法。DFS搜索的本质就是维护一个已经是完全图的子图,并在搜索过程中不断扩大这个子图,直到所有的点都被考虑进去。

DFS过程

    初始化:

     从一个点 u 开始,把这个点加入集合 U 中。将编号比它大的且和它相连的点加入集合 S1 中,为了方便,将集合 S1 中的点有序,让他们从小到大排列,进行第一遍 DFS

   第一遍 DFS :

     从 S1 中选择一个点 u1,遍历 S1 中,所有编号比 u1 大且和 u1 相连的点,其实也就是排在 u1 后面,并且和 u1 相连的点,将它们加入集合 S2 中。同理,让 S2 中的点也按照编号也从小到大排列。将 u1 加入集合 U 中,进行第二遍 DFS

   第二遍 DFS :

     从 S2 中选择一个点 u2,遍历 S2 中,所有排在 u2 后面且和 u2 相连的点,并把它们加入集合 S3 中,让 S3 中的点按照编号从小到大排列,将 u2 加入集合 U 中进行第三遍 DFS

   第三遍 DFS :

     从 S3 中选择一个点 u3,遍历 S3 中,所有排在 u3 后面且和 u3 相连的点,并把它们加入集合 S4 中,让 S4 中的点按照编号从小到大排列,将 u3 加入集合 U 中进行第四遍 DFS

   ……

   最底层的 DFS :

     当某个 S 集合为空时,DFS 过程结束,得到一个只用后面几个点构成的完全子图,并用它去更新只用后面几个点构成的最大团。退出当前 DFS,返回上层 DFS,接着找下一个完全子图,直到找完所有的完全子图

辅助集合

为每一层的DFS遍历开一个集合Sn,用来表示在这一层DFS中,考虑加入完全图中的点。从这个集合Sn中取一个点准备加入完全图,同时将和这个点相连的Sn中的其他点加入下一层DFS的辅助集合S(n+1)中去。

剪枝1

已经在完全图U中的点的数量 + 辅助集合Sn中的点的数量 < 已经产生的最优解,则不再进行后面的DFS

剪枝2

已经在完全图U中的点的数量 + 后面集合能够构成的最大完全子图的顶点数量 < 已经产生的最优解,则不再进行后面的DFS。

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

bool G[maxn][maxn];
int Max[maxn],Alt[maxn][maxn],ans,n;

bool dfs(int cur,int tot) //cur 当前层次集合大小 tot 所在的层次
{
if(cur==0)
{
if(tot>ans)
{
ans=tot;
return 1;
}
return 0;
}
for(int i=0;i<cur;i++)
{
if(cur-i+tot<=ans) return 0; //剪枝1
int u=Alt[tot][i]; //选取当前考虑的点
if(Max[u]+tot<=ans) return 0;
int next=0;
for(int j=i+1;j<cur;j++)
{
if(G[u][Alt[tot][j]]) Alt[tot+1][next++]=Alt[tot][j];
}
if(dfs(next,tot+1)) return 1;
}
return 0;
}

void MaxClique()
{
ans=0;
memset(Max,0,sizeof(Max));
for(int i=n-1;i>=0;i--) //逆向构建Max
{
int cur=0;
for(int j=i+1;j<n;j++)
if(G[i][j]) Alt[1][cur++]=j;
dfs(cur,1);
Max[i]=ans;
}
}

ZOJ 1492

题意

裸的最大团模板题

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
nclude <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <cmath>

using namespace std;

const int maxn=55;

bool G[maxn][maxn];
int Max[maxn],Alt[maxn][maxn],ans,n;

bool dfs(int cur,int tot) //cur 当前层次集合大小 tot 所在的层次
{
if(cur==0)
{
if(tot>ans)
{
ans=tot;
return 1;
}
return 0;
}
for(int i=0;i<cur;i++)
{
if(cur-i+tot<=ans) return 0; //剪枝1
int u=Alt[tot][i]; //选取当前考虑的点
if(Max[u]+tot<=ans) return 0;
int next=0;
for(int j=i+1;j<cur;j++)
{
if(G[u][Alt[tot][j]]) Alt[tot+1][next++]=Alt[tot][j];
}
if(dfs(next,tot+1)) return 1;
}
return 0;
}

void MaxClique()
{
ans=0;
memset(Max,0,sizeof(Max));
for(int i=n-1;i>=0;i--) //逆向构建Max
{
int cur=0;
for(int j=i+1;j<n;j++)
if(G[i][j]) Alt[1][cur++]=j;
dfs(cur,1);
Max[i]=ans;
}
}

void solve()
{
while(scanf("%d",&n)!=EOF)
{
if(n==0)break;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
int a;
scanf("%d",&a);
if(a==1)G[i][j]=1;
else G[i][j]=0;
}
MaxClique();
printf("%d\n",ans);
}
}

int main()
{
freopen("input.txt","r",stdin);
solve();
return 0;
}