圖論——二分圖


二分圖又稱作二部圖,是圖論中的一種特殊模型。

G=(V, E)是一個無向圖 如果G的頂點集V可分割為兩個互不相交的子集X和Y,並且E中每 條邊連接的兩個頂點一個在X中,另一個在Y中,則稱圖G為二分 圖,記為G=(X,Y,E)。

由定義可知,二分圖的這兩個部分中的任意兩個頂點之間沒有路徑

無向圖 G 為二分圖的充分必要條件是,G 至少有兩個頂點,且其所有回路的長度均為偶數。

 

Question:給定一個無向聯通圖,如何判定該圖是否為一個二分圖?

 

染色法:參考博文:http://www.cnblogs.com/1227xq/p/6826783.html

         https://www.cnblogs.com/digitalhermit/p/5119908.html

(1) 二分圖的判定 [鄰接表]

   對二分圖的結點進行染色。如果處於同一組,則應該染成同色,否則為不同色。在染色過程中,如果發生矛盾,說明此圖不是二分圖。

  這種方法就像塗顏色一樣,相鄰(若有邊相連)則染成不同色,反之染成同色,若矛盾,則false,反之true

下面為自作代碼+借鑒代碼

鄰接鏈表

#include<bits/stdc++.h>
#define N 100000

using namespace std;

struct node{
    int to,next;
}e[N];
int head[N],tot,n,m;
bool vis[N],colar[N];
void add(int u,int v){
    e[++tot].to=v;e[tot].next=head[u];head[u]=tot;
}

bool dfs(int k){
    vis[k]=1;
    for(int i=head[k],v;i,v=e[i].to;i=e[i].next){
        if(vis[v]==0){
               colar[v]=!colar[k];
        }else {
            if(colar[v]!=colar[k]) return true;
        }
    }
}//判斷函數退出,默認返回false

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int u,v;
          cin>>u>>v;add(u,v);add(v,u);
    }
    for(int i=1;i<=n;i++){
        if(vis[i]==0){
            if(dfs(i)==1){
                printf("NO\n");
                break;
            }
        }
    }printf("YES\n");
    return 0;
}
View Code

vector(STL神器)

#include<bits/stdc++.h>
#define N 100000

using namespace std;

vector<int>G[N];

int colar[N];

bool dfs(int u,int c){
    colar[u]=c;
    for(int i=0;i<G[u].size();i++){
        if(colar[G[u][i]]==c) return false;//如果相鄰的頂點同色,就剪掉這一枝,返回false
        if(colar[G[u][i]]==0&&!dfs(G[u][i],-c)) return false;//如果相鄰的頂點還沒有染色就把它染成-c
    }return true;
}

void solve(){
     for(int i=1;i<=n;i++){
        if(vis[i]==0){
            if(dfs(i,0)==0){
                printf("NO\n");
                return;
            }
        }
    }printf("YES\n");
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int u,v;
          cin>>u>>v;
          G[u].push_back(v);
          G[v].push_back(u);
    }solve();
    return 0;
}
View Code

這段代碼更容易理解:

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
#define N 1000
#define M 2000
int col[N],cnt,n,m,head[N],to[M],next[M];
void add(int x,int y){
    next[++cnt]=head[x];
    to[cnt]=y;
    head[x]=cnt;
}
void dfs(int u,int c){
    col[u]=c;
    for(int v,i=head[u];i;i=next[i]){
        v=to[i];
        if(!col[v])
          dfs(v,-c); // 染 -c 
        if(col[v]==col[u]){ // 有沖突 
            printf("NO");
            exit(0);
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int x,y,i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    for(int i=1;i<=n;i++)
        if(!col[i])
            dfs(i,1); // 默認 染色 1 
    printf("YES");
    return 0;
}
View Code

並查集(神奇的做法)

我們可以將每個節點抽象出兩個節點 v0,v1.

v0表示v染0這種顏色,v1表示v染1這種顏色。
對於這個新圖,
圖中的每一條邊(iq,jw)(I,j為原本的點編號,q,w為0或1),
表示i選q顏色的話,j一定要選w顏色。
#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
#define N 1000
int fa[N<<1],n,m;
int find(int k){return fa[k]==k?k:fa[k]=find(fa[k]);}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n*2;i++)fa[i]=i;
    for(int fx,fy,x,y,i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        
        fx=find(x*2-1);
        fy=find(y*2);
        fa[fx]=fy;//一個染白則另一個染黑 
        
        fx=find(x*2);
        fy=find(y*2-1);
        fa[fx]=fy;//一個染黑則另一個染白 
    }
    for(int i=1;i<=n;i++)
        if(find(i*2)==find(i*2-1)){//既染白又染黑 
            printf("NO");
            return 0;
        }
    printf("YES");
    return 0;
}
View Code

 

 

粘一波概念:

匹配:給定一個二分圖G=(X,Y,E),若存在E的一個子集M,滿足 M中的任意兩條邊都沒有公共頂點,則M稱為一個G的匹配

匹配邊:在匹配中的邊

未匹配邊:不在匹配中的邊

未匹配點:對於一個匹配,不與任何匹配邊鄰接的點

匹配點:剛好相反

極大匹配:無法再加邊的匹配
最大匹配:在所有極大匹配中,邊數|M|最大的匹配

完全匹配:如果一個匹配中,圖中每個頂點都與一條邊相關聯 ,則稱此匹配為完全匹配

 

下面給出關於二分圖最大匹配的三個定理:

① 最大匹配數+最大獨立集數=n

② 二分圖的最小覆蓋數=最大匹配數

③ 最小路徑覆蓋=最大獨立集 最大獨立集是指求一個二分圖中最大的一個點集,該點集內的點互不相連。 最小頂點覆蓋是指在二分圖中,用最少的點,讓所有的邊至少和一個點有關聯。 最小路徑覆蓋是指一個不含圈的有向圖 G 中,G 的一個路徑覆蓋是一個其結點不相交的路徑集合 P,圖中 的每一個結點僅包含於 P 中的某一條路徑。路徑可以從任意結點開始和結束,且長度也為任意值,包括 0。

增廣路

增廣路徑:若 P 是圖 G 中一條連通兩個未匹配頂點的路徑,並且屬於 M 的邊和不屬於 M 的邊(即已匹配 和待匹配的邊)在 P 上交替出現,則稱 P 為相對於 M 的一條增廣路徑。

由增廣路的定義可以推出下述三個結論:

① P 的路徑長度必定為奇數,第一條邊和最后一條邊都不屬於 M。

② P 經過取反操作可以得到一個更大的匹配 M'。

③ M 為 G 的最大匹配當且僅當不存在相對於 M 的增廣路徑。

 

方法(匈牙利算法):

求二分圖的最大匹配常用匈牙利算法,它是通過不斷地尋找增廣軌來實現的。很明顯,如果二分圖的兩部 分點分別為 X 和 Y,那么最大匹配的數目應該小於等於 min{X,Y}。因此我們可以枚舉某一部分里的每一個點, 從每個點出發尋找增廣軌,最后,該部分的點找完以后,就找到了最大匹配的數目。當然我們也可以通過記錄來找出這些邊。

 

匈牙利算法找一條增廣路的復雜度為 O(m),最多找 n 條增廣路,故時間復雜度為 O(mn)。


免責聲明!

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



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