回溯法——最大团问题
问题:
给定无向图G=(V,E)。如果U∈V,且对任意u,v∈U有(u,v)∈E,则称U是G的完全子图。
G的完全子图U是G的团当且仅当U不包含在G的更大的完全子图中。
G的最大团是指G中所含顶点数最多的团。
完全子图:任意两个顶点相连。团:不存在包含它的更大的完全子图。最大团:顶点数最多的团。
如果U∈V且对任意u,v∈U有(u,v)∉E,则称U是G的空子图。
G的空子图U是G的独立集当且仅当U不包含在G的更大的空子图中。
G的最大独立集是G中所含顶点数最多的独立集。
对于任一无向图G=(V,E),其补图G=(V1,E1)定义为:V1=V,且(u,v)∈E1当且仅当(u,v)∉E。
求原图的最大独立集等价于求补图的最大团。无向图G的最大团和最大独立集问题可以看作是图G的顶点集V的子集选取问题。如同装载问题。
分析:
与装载问题是类似的,都是1-0子集树。约束函数:每个选了的顶点之间两两相连。限制函数:已经选了的点加上剩下的点大于当前的最优解。
代码:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 105
// 用邻接矩阵来存储图
int m[MAXN][MAXN];
// 最优解
int bestGroup = -1;
// 结果记录数组
int res[MAXN];
// 最终结果数组
int bestRes[MAXN];
// 约束函数
bool constrain(int pos){
for(int i = 1;i <= pos;i++){
if(res[i]){
for(int j = 1;j <= pos;j++){
if(j == i) continue;
if(res[j] && !m[i][j]){
return false;
}
}
}
}
return true;
}
// 计算当前解
int sum(int pos){
int tmp = 0;
for(int i = 1;i <= pos;i++){
if(res[i]) tmp++;
}
return tmp;
}
// 限界函数
bool bound(int pos,int n){
return sum(pos) + n - pos > bestGroup;
}
// 递归求解最大团问题
void maxGroupTraceback(int pos,int n){
// 找到了一个最优解
if(pos > n){
bestGroup = sum(n);
for(int i = 0;i <= n;i++)
bestRes[i] = res[i];
}else{
// 左子树
res[pos] = 1;
if(constrain(pos)){
maxGroupTraceback(pos+1, n);
res[pos] = 0;
}
res[pos] = 0;
if(bound(pos,n)){
maxGroupTraceback(pos+1, n);
}
}
}
int main(){
int n = 5;
memset(m, 0, sizeof(m));
memset(res, 0, sizeof(res));
memset(bestRes, 0, sizeof(bestRes));
m[1][2] = 1;
m[1][4] = 1;
m[1][5] = 1;
m[2][3] = 1;
m[2][5] = 1;
m[3][5] = 1;
m[4][5] = 1;
m[2][1] = 1;
m[4][1] = 1;
m[5][1] = 1;
m[3][2] = 1;
m[5][2] = 1;
m[5][3] = 1;
m[5][4] = 1;
maxGroupTraceback(1, n);
for(int i = 1;i <= n;i++){
if(bestRes[i]) cout<<i<<" ";
}
cout<<endl<<bestGroup<<endl;
}