• Bron–Kerbosch算法最大独立集与最大团


    ---恢复内容开始---

    Bron-Kerbosch 算法计算图的最大全连通分量(团clique) 

    最大独立集: 顶点集V中取 K个顶点,其两两间无连接。

    最大团: 顶点集V中取 K个顶点,其两两间有边连接。

    最大团中顶点数量 = 补图的最大独立集中顶点数量

    补图定义: 

             G = <V, E>

        the complement of G, \bar{G} = <V, V \times V - E> 
        
        详见连接: http://zh.wikipedia.org/wiki/%E8%A3%9C%E5%9C%96 
        更详细的: http://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm
        
    就可以通过求其补图中最大团中顶点数量,就可得出原图中最大独立集中顶点数量了.
     
    对于求解 最大团中顶点数量 的搜索过程中用到的剪枝,如下
    1. 剪枝1:常用的指定顺序, 即枚举第i个顶后, 以后再枚举时枝考虑下标比大它的, 避免重复。
    2. 剪枝2:自己开始从前往后的枚举顶点, TLE两次. 后来从后往前枚举顶点,发现可以利用顶点之间的承袭性.我用num[i] 记录的可选顶点集合为 V[i, i+1, ... , n] 中的最大团数目, 目标是求num[1].
         分析易知, num[i] = num[i+1] 或者 num[i]+1   (num[1...n] 具有非降的单调性,从后往前求)
         由这个式子以及num[]信息的记录,使得我们可以增加两处剪枝:
    3.上/下剪枝:假设当前枚举的是顶点x, 它的第一个邻接顶是i (标号一定比x大,即num[i]已经求出) 我们可以知道, 若 1 + num[i] <= best, 那么是没没要往下枚举这个顶点x了,因为包含它的团是不可能超过我们目前的最优值的。
    4. 立即返回剪枝: 由于num[i]最大可能为num[i+1]+1, 所以在枚举顶点i时,只要一更新best,可知此时的num[i]就为num[i+1]+1了,不需要再去尝试找其他的方案了,所以应立即返回.

    比较容易理解得C/C++代码:

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    
    int best;
    int num[maxn];
    // int x[maxn];
    int path[maxn]; 
    int g[maxn][maxn], n;
    
    bool dfs( int *adj, int total, int cnt ){ // total: 与u相连的顶点数量  , cnt表示当前团的数量 
        int i, j, k;
        int t[maxn];
        if( total == 0 ){ // 当此团中最后一个点 没有 比起序号大 的顶点相连时  
            if( best < cnt ){  // 问题1:best为最大团中顶点的数量 
                // for( i = 0; i < cnt; i++) path[i] = x[i];
                best = cnt; return true; 
            }    
            return false;
        }    
        for( i = 0; i < totl; i++){ // 枚举每一个与 u 相连的顶点 adj[i] 
            if( cnt+(total-i) <= best ) return false; // 剪枝1, 若当前 顶点数量cnt 加上还能够增加的最大数量 仍小于 best则 退出并返回false 
            if( cnt+num[adj[i]] <= best ) return false; // 剪枝2, 若当前 顶点数量cnt 加上 包含adj[i]的最大团顶点数 仍小于 best则 退出并返回false 
            // x[cnt] = adj[i];
            for( k = 0, j = i+1, j < total; j++ ) // 扫描 与u相连的顶点  中与 adj[u]相连的顶点 并存储到 数组 t[]中,数量为k 
                if( g[ adj[i] ][ adj[j] ] )
                    t[ k++ ] = adj[j];
                    if( dfs( t, k, cnt+1 ) ) return true;
        } return false;
    } 
    int MaximumClique(){
        int i, j, k;
        int adj[maxn];
        if( n <= 0 ) return 0;
        best = 0;
        for( i = n-1; i >= 0; i-- ){
            // x[0] = i; 
            for( k = 0, j = i+1, j < n; j++ )    // 遍历 [i+1, n] 间顶点,  
                if( g[i][j] ) adj[k++] = j;
            dfs( adj, k, 1 ); // *adj, total, cnt
            num[i] = best;   // 得出顶点 i, 出发构成最大团 中顶点数量 
        }    
        return best;
    }
    关于 Bron-Kerbosch算法
      基础形式是一个递归回溯的搜索算法.通过给定三个集合 (R,P,X).
      初始化集合R,X分别为空,而集合P为所有顶点的集合.
      而每次从集合P中取顶点{v}, 当集合中没有顶点时,两种情况.
        1.  集合 R 是最大团, 此时集合X为空.
        2.  无最大团,此时回溯.
      对于每一个从集合P中取得得顶点{v},有如下处理:
        1. 将顶点{v}加到集合R中, 集合P,X 与 顶点{v}得邻接顶点集合 N{v}相交, 之后递归集合 R,P,X
        2. 从集合P中删除顶点{v},并将顶点{v}添加到集合X中.
        若 集合 P,X都为空, 则集合R即为最大团.
        总的来看就是每次从 集合P中取v后,再在 P∩N{v} 集合中取,一直取相邻,保证集合R中任意顶点间都两两相邻...
     
    伪代码过程:
     BronKerbosch1(R,P,X):
           if P and X are both empty:
               report R as a maximal clique
           for each vertex v in P:
               BronKerbosch1(R ⋃ {v}, P ⋂ N(v), X ⋂ N(v))
               P := P \ {v}
               X := X ⋃ {v}

    对于这个基础的算法,效率不高,因为其递归搜索了所有情况,其中有些不是最大团的也进行了搜索.

    为了节省时间和让算法更快的回溯,我们可以通过设定关键点'pivot'{u},通过简单分析,我们知道.

    对于任意的最大团,其必须包括顶点{u}或者Non-N{u},(反面关系).不然其必然需要通过添加它们来进行扩充,这显然矛盾.所以.我们仅仅需要测试 顶点{u}以及 Non-N{u}即可.这样可以节省递归的时间.

    伪代码过程

     BronKerbosch2(R,P,X):
           if P and X are both empty:
               report R as a maximal clique
           choose a pivot vertex u in P ⋃ X
           for each vertex v in P \ N(u):
               BronKerbosch2(R ⋃ {v}, P ⋂ N(v), X ⋂ N(v))
               P := P \ {v}
               X := X ⋃ {v}

    疑问,若 P \ N(u) 为空, 则所有顶点皆与u相邻,则此时应该将u加入最大团则为最优...

     因为通过选择特殊点,是算法最小化递归调用,所以一定程度上节省了时间.
     
    另外一种方法是用过放弃选择特殊点,而是利用降序的方式,保证在线性的时间求的子图的.
    其实这里也可以用特殊点结合起来,效果会更优.
    最大团.
     
    伪代码过程
    BronKerbosch3(G):
           P = V(G)
           R = X = empty
           for each vertex v in a degeneracy ordering of G:
               BronKerbosch2(R ⋃ {v}, P ⋂ N(v), X ⋂ N(v))
               P := P \ {v}
               X := X ⋃ {v}

    ---恢复内容结束---

    第三类优化模板 C/C++实现: 

    #include<cstdio>
    #include<cstring>
    #define N 1010
    bool flag[N], a[N][N];
    int ans, cnt[N], group[N], n, vis[N];
    // 最大团: V中取K个顶点,两点间相互连接
    // 最大独立集: V中取K个顶点,两点间不连接 
    // 最大团数量 = 补图中最大独立集数
     
    bool dfs( int u, int pos ){
        int i, j;
        for( i = u+1; i <= n; i++){
            if( cnt[i]+pos <= ans ) return 0;
            if( a[u][i] ){
                 // 与目前团中元素比较,取 Non-N(i) 
                for( j = 0; j < pos; j++ ) if( !a[i][ vis[j] ] ) break; 
                if( j == pos ){     // 若为空,则皆与 i 相邻,则此时将i加入到 最大团中 
                    vis[pos] = i;
                    if( dfs( i, pos+1 ) ) return 1;    
                }    
            }
        }    
        if( pos > ans ){
                for( i = 0; i < pos; i++ )
                    group[i] = vis[i]; // 最大团 元素 
                ans = pos;
                return 1;    
        }    
        return 0;
    } 
    void maxclique()
    {
        ans=-1;
        for(int i=n;i>0;i--)
        {
            vis[0]=i;
            dfs(i,1);
            cnt[i]=ans;
        }
    }
  • 相关阅读:
    南京航空航天大学软件著作权申请办法
    CoDel Test Script
    [编辑中] 免费的Internet流量发生器 | Free Internet Traffic Generators
    关于Java LDAP登录集成
    sonar + ieda实现提交代码前代码校验
    sonar+Jenkins代码覆盖率检测
    定义自己的代码风格CheckStyle简单使用
    HAProxy简单使用
    读取大文件性能测试
    使用HtmlUnit登录百度
  • 原文地址:https://www.cnblogs.com/yefeng1627/p/2991592.html
Copyright © 2020-2023  润新知