无向图的最大团/最大独立集 算法总结

无向图的最大团/最大独立集 算法总结

目录

  1. 概述
  2. 普通DFS
  3. Bron-Kerbosch算法

概述

最大团其实就是最大完全子图的点集,同理极大团也就是极大完全子图的点集,点集内所有的点两两之间都有边相连。

定义 (维基百科):

在图论领域的一个无向图中,满足两两之间有边连接的顶点的集合,被称为该无向图的团。

最大独立集其实就是补图的最大团,因为和最大团相反,最大独立集合内的点两两之间都没有边相连。

定义(维基百科):

一个独立集(也称为稳定集)是一个图中一些两两不相邻的顶点的集合。

普通DFS

该方法很简单粗暴,就是一个个节点尝试。维护一个当前团,每个节点都有两种状态(在当前团中或不在当前团中),如果无法再往里加节点了的话,那么当前团就成为了一个极大团,这样找到所有的极大团并作出比较取舍即可得到最大团。但是这样的时间复杂度达到了O( 2 n 2^n 2n),所以我们需要做一个剪枝,就是判断还没有入团且没有被排除过的点如果全部加入当前团也没办法达到当前最大团的大小的时候就跳出,但是这样就不能保证找到所有的极大团了,只能保证能够得到最大团。

代码

HDU1530 AC确认,该题纯模板题输入是直接输入了邻接矩阵

#include 
#include 
#include 
#include 
/*
* 潘骏同志瞎几把模拟的DFS求最大团
* 本板子剪过枝了但是还是过不了POJ 2989
*/
using namespace std;
bool maps[105][105];//邻接矩阵,判断有没有边相连是O(1)的比较方便
vector best;//当前最大团
vector now;//当前尝试插入的团
int ans;
int n;
void dfs(int pos){
    if(pos==n+1){//所有的点都过了一遍,没有可插入的点了
        if(now.size()>best.size()){
            best=now;
            ans=1;
        } else if(now.size()==best.size()){
            best=now;
            ans++;
        }
        return;
    }
    bool flag=true;
    for(int i=0;i=best.size()){//剪枝,如果剩下的点全都加入也不能使最大团增大就退出
        if(flag){
            now.push_back(pos);
            dfs(pos+1);//尝试该点插入状态
            now.pop_back();
        }
        dfs(pos+1);//尝试该点未插入状态
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    while(cin>>n && n!=0){
        now.clear();
        best.clear();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                cin>>maps[i][j];
        dfs(1);
        cout<

Bron-Kerbosch 算法

该算法本质也是DFS。引入三个集合,all集合,some集合,none集合,其中some集合代表待检查且可能能加入团的节点,none集合代表已经检查过且我们认为不能加入团的节点,all则是检查过并且我们认为能加入最大团的节点,当some集合和none集合都为空的时候,all集合即为我们需要求的极大团。每次我们检查some集合里的一个节点,并把它加入all集合,那么可能加入待检查序列的就是它自己的邻接点(毕竟要保证团内所有点都有边相连),和some集合做一个交集,none集合同样和邻接点们作交集,以保证some和none里面的点和all里的点都有边相连。把当前检查点从all里拿出来以后就放入none里面,再从some集合的下一个开始检查。some集合为空的时候就没有可以加入all集合的点了,some集合为空且none集合不为空的时候就代表none集合里面的点放进all集合团会更大(之前检查过这种情况),所以一定要两个集合都为空才行。

这里还要介绍一种优化方法,因为不加这个优化可能连板子题都会TLE。如果我们要从some里选一个点pivot加入到all里的话,它的邻接点势必会因为和some作交集而留在some里待检查,在下一层DFS里势必会被检查,所以没有必要在检查pivot的这一层里再以它们为起点去检查,这会导致重复。选pivot的时候也可以以度数的大小选度数最大的点,这样能减少的检查次数也最多。

时间复杂度最差是O( 3 ( n / 3 ) 3^(n/3) 3(n/3)) 证明(英文论文我也没看完,就看了个结论)

代码

POJ 2989 AC确认,裸板题加了个大于1000判断,不加pivot TLE,用STL 188ms,数组 47ms,度数排序32 ms

#include 
#include 
#include 
#include 
#include 
/*
* 潘骏同志瞎几把模拟的Bron-Kerbosch算法
* 本板子已优化到最佳
*/
using namespace std;
int some[130][130];//别用STL了,这题里就能省一半还多的时间了
int all[130][130];//这里的数组如上所述
int none[130][130];
bool maps[130][130];//邻接矩阵,判断有没有边相连是O(1)的比较方便
int degrees[130];//记录各个节点的度数
int ans;
bool cmp(int a,int b){
    return degrees[a]>degrees[b];//根据度数排序,度数最大的可以作为pivot点
}
void BronKerbosch(int pos,int al,int so,int no){
    if(so==0 && no==0){
        ans++;//这里的all[pos]即为我们极大团,要求最大团只需要比较一下al大小即可。
        return;
    }
    int pivot;
    if(so!=0){
        pivot=some[pos][0];//排过序了,所以当前some集合的第一个元素是度数最大的点
        for(int i=0;i1000)
            return;
        some[pos][i]=-1;none[pos][no++]=nxt;//从some中移除已经调查过的点并移入none
    }
}
int main(){
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF){
        memset(some,0,sizeof some);
        memset(all,0,sizeof all);
        memset(none,0,sizeof none);
        memset(degrees,0,sizeof degrees);
        memset(maps,0,sizeof maps);
        int so=0;
        for(int i=1;i<=n;i++)
            some[0][so++]=i;//一开始some集合要包含所有节点
        for(int i=0;i1000){
            printf("Too many maximal sets of friends.\n");
        }
        else{
            printf("%d\n",ans);
        }
    }
    return 0;
}

参考了以下文章,表示感谢:
维基百科Bron-Kerbosch算法
https://blog.csdn.net/yo_bc/article/details/77453478

你可能感兴趣的:(2018年算法训练)