二分圖的判定,最大匹配
<1>.什麼是二分圖
如果一個圖的頂點可以分為兩個集合X和Y,圖的所有邊一定是有一個頂點屬於集合X,另一個頂點屬於集合Y,則稱該圖為“二分圖”( Bipartite Graph )
<2>.二分圖的判定
- 如果一個圖是連通的,可以用如下的方法判定是否是二分圖:
- 在圖中任選一頂點v,定義其距離標號為0,然後把它的鄰接點的距離標號均設為1,接著把所有標號為1的鄰接點均標號為2(如果該點未標號的話),如圖所示,以此類推。
- 標號過程可以用一次BFS實現。標號後,所有標號為奇數的點歸為X部,標號為偶數的點歸為Y部。
- 接下來,二分圖的判定就是依次檢查每條邊,看兩個端點是否是一個在X部,一個在Y部。
(如果一個圖不連通則在每個連通塊中作判定)
演算法實現;
#include <iostream> #include <cstring> #include <algorithm> #include <cstdio> #include <queue> #include <vector> #include <cmath> #include <set> #include <deque> #include <map> #include <string> using namespace std; const int maxn=200+10; //結點個數 任意值 int jilu[maxn],erfen[maxn][maxn]; //BFS遍歷圖 bool bfs(int s,int n){ queue<int> q; q.push(s); jilu[s]=1; while(!q.empty()){ int from=q.front(); q.pop(); for(int i=1;i<=n;++i){ if(erfen[from][i]&&jilu[i]==-1){ q.push(i); jilu[i]=!jilu[from]; } if(erfen[from][i]&&jilu[from]==jilu[i]) return false; } } return true; } int main(){ // freopen("C:\\Users\\24398\\Desktop\\in-project.txt","r",stdin); int n,m,a,b; memset(jilu,0,sizeof(jilu)); scanf("%d%d",&n,&m); //n頂點個數 m邊數集 for(int i=0;i<m;++i){ //採用鄰接矩陣儲存 intput() scanf("%d%d",&a,&b); erfen[a][b]=erfen[b][a]; } bool panduan=false; for(int j=0;j<n;++j){ if(erfen[j]==0&&bfs(j,n)){ panduan=true; //遍歷每一個連通分支 break; } } //output() if(panduan) printf("二分圖\n"); else printf("非二分圖\n"); return 0; }
<3>.最大匹配
匹配定義; 令G(V,E) 是一個無向圖,若存在邊集M ,使得M中所有的邊都沒有公共頂點,則稱M是G的一個匹配(matching)。
最大匹配;邊數最多的匹配稱為最大匹配
完美匹配;,如果所有的點都在匹配邊上,稱這個最大匹配是完美匹配
增廣路徑
如果P是圖G中一條連通兩個未匹配頂點的路徑(P以未匹配點為起點和終點的路徑),並且屬M的邊和不屬於M的邊(即已匹配和待匹配的邊)在P上交替出現,則稱P為現對於M的一條增廣路徑
二分圖的最大匹配-增廣路徑
令M是G的一個匹配,則P是M的一條增廣路徑;則是G的一個大小為|M|+1的匹配。
新增的邊或者是M的邊,或者是P的邊,但不是即在M的邊,也在P的邊。
舉例;
邊集;M={(b,c),(e,f),(i,j),(h,l)}
推出三個結論;
1)P的路徑長度必定為奇數,第一條邊和最後一條邊都不屬於M
2)P經過取反操作可以得到一個更大的匹配M
3) M為G的最大匹配當且僅當不存在相對於M的增廣路徑
<4>.匈牙利演算法(求二分圖的最大匹配)
<匈牙利樹>;一般由 BFS 構造(類似於 BFS 樹)。從一個未匹配點出發運行 BFS(唯一的限制是,必須走交替路),直到不能再擴充套件為止。例如,由圖 1,可以得到如圖 2的一棵 BFS 樹,如果所有的葉子都是匹配點,則這課BFS樹是匈牙利樹
演算法實現
(1). 置M(匹配)為空
(2). 找出一條增廣路徑P,通過取反操作獲得更大的匹配M’代替M
(3). 重複(2)操作直到找不出增廣路徑為止。
>>>細節
對於二分圖A側(可以把點少的一側看作A側)的每個點,依次如下操作:
1.設定A[i]為當前點。
2.用A側和B側的點輪流進行如下操作。
(1)對A側的點A[i]:找到一條沒有匹配的邊。如果邊的B側的點不屬於匹配中另外的邊,那麼這個時候表示增廣路徑找到了(初始情況下直接相連的一條邊的最大匹配就屬於這種情況),修改最大匹配。如果邊的B側的點屬於匹配中另外的邊,設B側的點為B[j],則進入(2)。如果這些都做不到,需要進行回溯。如果回溯到了起點,表明從起點A[i]找增廣路徑失敗了。注意,能否找到增廣路徑全由A側的點決定(找邊和回溯),B側的點做的操作非常簡單。
(2)對B側的點B[j],由於是找增廣路徑,已匹配的邊需要用到,故直接找到B[j]所在的匹配邊在A側對應的點A[k],把A[k]置為當前點繼續進行步驟(1)。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <vector>
#include <cmath>
#include <set>
#include <deque>
#include <map>
#include <string>
using namespace std;
/*
匈牙利樹 (BFS構造)
find() 匈牙利演算法實現
main()主函式 基本輸入格式
*/
const int maxn=1010; //假設頂點數
int erfen[maxn][maxn];
int used[maxn],d[maxn];
int n,m;
bool find(int x)
{
for(int i=0;i<n;i++)
{
if(erfen[x][i]==true&&used[i]==false)
{
used[i]=1; //標記
if(d[i]==0||find(d[i])) //遞迴實現
{
d[i]=x;
return true;
}
}
}
return false;
}
int main()
{
// freopen("C:\\Users\\24398\\Desktop\\in-project.txt","r",stdin);
while(scanf("%d",&n)!= EOF)
{
int x,y;
int sum=0;
memset(d,0, sizeof(d));
memset(erfen,0, sizeof(erfen));
for(int i=0;i<n;i++)
{
scanf("%d: (%d) ",&x,&m);
while(m--)
{
scanf("%d,",&y);
erfen[x][y]=1;
}
}
for(int j=0;j<n;j++)
{
memset(used,0, sizeof(used));
if(find(j))
sum++;
}
printf("%d\n",n-sum/2);
}
return 0;
}