最大团
1.算法分析
概念: 当G′是图G的子图,且G′是关于V′的完全图时,子图G'为图G的团;当G'是团,且不是其他团的子集时,G'为图G的极大团;当G'是极大团时,且点数最多,G'为图G最大团
一般无向图的最大独立集=补图的最大团
求最大团使用Bron–Kerbosch算法,时间O(3^(n/3))
基本思想就是dfs+剪枝
2. 典型例题
2.1 板子题
ZOJ 1492 Maximum Clique
#include <bits/stdc++.h>
int const N = 100;
int g[N][N], group[N], vis[N], cnt[N], n, ans;
using namespace std;
// u当前节点,pos当前最大团内的点数
bool dfs( int u, int pos )
{
int i, j;
for( i = u + 1; i <= n; i++) // 遍历其他的点
{
if( cnt[i] + pos <= ans ) return 0; // 可行性剪枝
if( g[u][i] ) // 必须有连边
{
for( j = 0; j < pos; j++ ) if( !g[i][ vis[j] ] ) break; // 判断这个最大团内其他的点是否和i都有连边
if( j == pos ) // 如果这个团内的点都和i点有连边
{
vis[pos] = i; // 把i点放入最大团
if( dfs( i, pos+1 ) ) return 1; // 判断以i为起点的最大团是否存在
}
}
}
// 记录最大团内的所有点
if( pos > ans ) // 如果比原先的最大团大
{
for( i = 0; i < pos; i++ )
group[i] = vis[i];
ans = pos;
return 1;
}
return 0;
}
// 求最大团
int maxclique()
{
ans = -1; // 记录这张图的最大团
for (int i = n; i > 0; --i) // 从n开始是为了保证先得到以点数大的点开始的最大团,使得后续好继承它的状态,利于剪枝
{
vis[0] = i; // 一开始最大团内点为i
dfs(i, 1); // 从i点开始dfs
cnt[i] = ans; // 更新以i为起点的最大团内的点数
}
return ans;
}
int main()
{
while (cin >> n && n)
{
/* code */
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
cin >> g[i][j];
cout << maxclique() << endl;
}
return 0;
}
2.2 二分+最大团
HDU3385 maximum shortest distance
给了平面上 n 个点,要求选出 k 个点来,使得这 k 个点中,距离最近的两个点的距离最大。n 最大为50
/*
二分答案后,如果两个点之间的距离大于当前的判断值,加边,在用最大团跑一下,
根据得到最大团点的数量和 k 的大小关系,调整二分的上下界
*/
#include <bits/stdc++.h>
using namespace std;
int const N = 100;
int group[N], vis[N], cnt[N], n, ans, m;
pair<double, double> point[N];
double g[N][N];
double eps = 1e-5;
// u当前节点,pos当前最大团内的点数
bool dfs( int u, int pos )
{
int i, j;
for( i = u + 1; i <= n; i++) // 遍历其他的点
{
if( cnt[i] + pos <= ans ) return 0; // 可行性剪枝
if( g[u][i] ) // 必须有连边
{
for( j = 0; j < pos; j++ ) if( !g[i][ vis[j] ] ) break; // 判断这个最大团内其他的点是否和i都有连边
if( j == pos ) // 如果这个团内的点都和i点有连边
{
vis[pos] = i; // 把i点放入最大团
if( dfs( i, pos+1 ) ) return 1; // 判断以i为起点的最大团是否存在
}
}
}
// 记录最大团内的所有点
if( pos > ans ) // 如果比原先的最大团大
{
for( i = 0; i < pos; i++ )
group[i] = vis[i];
ans = pos;
return 1;
}
return 0;
}
// 求最大团
int maxclique()
{
ans = -1; // 记录这张图的最大团
for (int i = n; i > 0; --i) // 从n开始是为了保证先得到以点数大的点开始的最大团,使得后续好继承它的状态,利于剪枝
{
vis[0] = i; // 一开始最大团内点为i
dfs(i, 1); // 从i点开始dfs
cnt[i] = ans; // 更新以i为起点的最大团内的点数
}
return ans;
}
double dis(pair<double, double> x, pair<double, double> y)
{
return (y.first - x.first) * (double)(y.first - x.first) + (y.second - x.second) * (double)(y.second - x.second);
}
void build(double limit)
{
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
{
if (i == j) continue;
double dist = sqrt(dis(point[i], point[j]));
if (dist >= limit) g[i][j] = 1;
}
}
}
// 判断距离最小的边最大是多少
bool check(double limit)
{
memset(g, 0, sizeof g);
build(limit); // 两点间距离大于limit才能建图
int ans = maxclique(); // 最大团内点数
if (ans >= m) return true;
else return false;
}
int main()
{
while(cin >> n >> m)
{
for (int i = 1; i <= n; ++i)
{
cin >> point[i].first >> point[i].second;
}
// 二分答案
double l = 0, r = 20000.0;
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) l = mid; // 当前最大团内点格式大于等于m,限制值设置太小
else r = mid;
}
printf("%.2lf
", l);
}
return 0;
}
2.3一般无向图最大独立集
POJ 1419 Graph Coloring
给了一个有 n 个点 m 条边的无向图,要求用黑、白两种色给图中顶点涂色,相邻的两个顶点不能涂成黑色,求最多能有多少顶点涂成黑色。图中最多有 100 个点
/*最大团点的数量=补图中最大独立集点的数量。建立补图,求最大团即可*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
int const N = 100;
int g[N][N], group[N], vis[N], cnt[N], n, ans, t, m;
using namespace std;
// u当前节点,pos当前最大团内的点数
bool dfs( int u, int pos )
{
int i, j;
for( i = u + 1; i <= n; i++) // 遍历其他的点
{
if( cnt[i] + pos <= ans ) return 0; // 可行性剪枝
if( g[u][i] ) // 必须有连边
{
for( j = 0; j < pos; j++ ) if( !g[i][ vis[j] ] ) break; // 判断这个最大团内其他的点是否和i都有连边
if( j == pos ) // 如果这个团内的点都和i点有连边
{
vis[pos] = i; // 把i点放入最大团
if( dfs( i, pos+1 ) ) return 1; // 判断以i为起点的最大团是否存在
}
}
}
// 记录最大团内的所有点
if( pos > ans ) // 如果比原先的最大团大
{
for( i = 0; i < pos; i++ )
group[i] = vis[i];
ans = pos;
return 1;
}
return 0;
}
// 求最大团
int maxclique()
{
ans = -1; // 记录这张图的最大团
for (int i = n; i > 0; --i) // 从n开始是为了保证先得到以点数大的点开始的最大团,使得后续好继承它的状态,利于剪枝
{
vis[0] = i; // 一开始最大团内点为i
dfs(i, 1); // 从i点开始dfs
cnt[i] = ans; // 更新以i为起点的最大团内的点数
}
return ans;
}
int main()
{
cin >> t;
while (t--)
{
cin >> n >> m;
memset(g, 1, sizeof g); // 1认为是有边
for (int i = 1, a, b; i <= m; ++i)
{
scanf("%d %d", &a, &b);
g[a][b] = g[b][a] = 0; // 建立补图
}
cout << maxclique() << endl;
for (int i = 0; i < ans; ++i) printf("%d ", group[i]);
cout << endl;
}
return 0;
}