1. 程式人生 > 其它 >一般圖最大獨立集

一般圖最大獨立集

一、定義:

獨立集:在一個圖中,找到一個集合包含的所有點相互之間都不存在連邊

最大獨立集:在所有獨立集中包含元素個數最多的獨立集

二、處理問題的第一步:問題轉化:

需要用最大團來求最大點獨立集,因此先引入最大團的概念

最大團問題

tips:最大團和強連通分量有區別,最大團U要求U成為最大的點集,且點集內任意兩點都連通,而強連通分量U要求U成為最大的點集,且滿足點集內任意兩點都有一條互相到達的路徑。
最大的區別在於是否關注連線兩點的路徑的方向,最大團不關注路徑的方向,只要有路徑連線即可,而強連通分量關注路徑的方向,既要能從一個點到另一個點,又能從另一個點回到該點。

最大團的求法

最大團問題與最大獨立集問題的關係


求解一個圖中的最大獨立集等價於求解其補圖的最大團。

補圖就是把原圖中所有邊都刪去,把所有原本沒有邊直接連線的兩個點之間都連上邊。(字面意思,很好理解)

獨立集的條件是任意兩個點互不連通,那麼如果把原圖中連通的點之間的邊刪除,不連通點連線,即轉化為求個數最多的兩兩連通點集,也即求最大團。

問題現在已經轉化為了求最大團,用一般的最大團演算法完成就可以

三、處理問題的第二步:去學最大團的求法 模版來源

簡單來說,極大團是增加任一頂點都不再符合定義的團,最大團是圖中含頂點數最多的極大團,最大獨立集是除去圖中的團後的點集,而最大團問題就是在一個無向圖中找出一個點數最多的完全圖。

Bron-Kerbosch 演算法

1、演算法原理:

Bron-Kerbosch 演算法的基礎形式是一個遞歸回溯的搜尋演算法,其通過給定三個集合:R、P、X 來遞迴的進行搜尋

<1>初始化集合 R、X 分別為空,集合 P 為所有頂點的集合
<2>每次從集合 P 中取頂點 {vi},當集合中沒有頂點時,有兩種情況:
······1)集合 R 是最大團,此時集合 X 為空
······2)無最大團,此時回溯
<3>對於每一個從集合 P 中取得的頂點 {vi},有如下處理:
······1)將頂點 {vi} 加到集合 R 中,集合 P、X 與頂點 {vi} 得鄰接頂點集合 N{vi} 相交,之後遞迴集合 R、P、X
······2)從集合 P 中刪除頂點 {vi},並將頂點 {vi} 新增到集合 X 中
······3)若集合 P、X 都為空,則集合 R 即為最大團

總的來看,就是每次從集合 P 中取 vi 後,再從 P∩N{vi} 集合中取相鄰結點,保證集合 R 中任意頂點間都兩兩相鄰
2、演算法優化

對於基礎的演算法,由於其遞迴搜尋了所有情況,對其中有些不是最大團的也進行了搜尋,效率不高,為了節省時間讓演算法更快的回溯,可以通過設定關鍵點來進行搜尋。

由於對於任意的最大團,其必須包括頂點 {u} 或 N-N{u},不然其必然需要通過新增它們來進行擴充,這顯然矛盾,所以僅需測試頂點 {u} 以及 N-N{u} 即可。

由於其是通過選擇特殊點,來進行最小化遞迴呼叫,一定程度上節省了時間,但還可以與降序的方式結合使用,來保證線上性的時間內求子圖的最大團

Code

int n,m;
bool G[N][N];
int cnt[N];//cnt[i]為>=i的最大團點數
int group[N];//最大團的點
int vis[N];//記錄點的位置
int res;//最大團的數目
bool dfs(int pos,int num){//num為當前獨立集中的點數
    for(int i=pos+1;i<=n;i++){
        if(cnt[i]+num<=res)//剪枝,若取i但cnt[i]+已經取了的點數仍<ans
            return false;
 
        if(G[pos][i]){//與當前團中元素比較,取Non-N(i)
            int j;
            for(j=0;j<num;j++)
                if(!G[i][vis[j]])
                    break;
            if(j==num){//若為空,則皆與i相鄰,則此時將i加入到最大團中
                vis[num]=i;
                if(dfs(i,num+1))
                    return true;
            }
        }
    }
 
    if(num>res){//每新增一個點最多使最大團數+1,後面的搜尋就沒有意義了
        for(int i=0;i<num;i++)//最大團的元素
            group[i]=vis[i];
        res=num;//最大團中點的數目
        return true;
    }
    return false;
}
void maxClique(){
    res=-1;
    for(int i=n;i>0;i--){//列舉所有點
        vis[0]=i;
        dfs(i,1);
        cnt[i]=res;
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        memset(G,0,sizeof(G));
 
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            G[x][y]=1;
            G[y][x]=1;
        }
 
        //建立反圖
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j)
                    G[i][j]=0;
                else
                    G[i][j]^=1;
            }
        }
        maxClique();
 
        if(res<0)
            res=0;
        printf("%d\n",res);//最大團的個數
        for(int i=0;i<res;i++)//最大團中的頂點
            printf("%d ",group[i]);
        printf("\n");
    }
    return 0;
}