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;
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM