二分圖又稱作二部圖,是圖論中的一種特殊模型。
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; }
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; }
這段代碼更容易理解:

#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; }
並查集(神奇的做法)
我們可以將每個節點抽象出兩個節點 v0,v1.

#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; }
粘一波概念:
匹配:給定一個二分圖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)。