二分圖的基本知識:
對於無向圖G 如果頂點V可分割爲兩個互不相交的子集(A,B),並且圖中的每條邊(i,j)所關聯的兩個頂點i和j分別屬於這兩個不同的頂點集,則稱圖G爲一個二分圖
二分圖常見問題與名詞
1 最大匹配 每個頂點至多連接一條邊 找到邊數最多的情況
最大匹配的邊數稱爲最大匹配數
2 完美匹配 圖中每個頂點都連接一條邊
3最優匹配(最大權完美匹配) 在帶權圖上求一個匹配 使邊權之和最大
另:二分圖有一些其他問題都可以轉化爲與最大匹配有關的問題
二分圖建圖:
0
1 染色法
Bfs 起點爲0 鄰接結點染色爲1
1的鄰接結點染色爲0
0的鄰接結點染色爲1
如果染色過了加上標記
如果染色過了 0鄰接0 則不爲二分圖 /或者1鄰接1
匈牙利算法 二分圖最大匹配:
一些概念:
交錯路(交替路)從一個未匹配點出發,依次經過非匹配邊、匹配邊、非匹配邊...形成的路徑 這些邊都不重複 路徑上的點也不重複
增廣路:從未匹配點出發 走交替路 到達一個未匹配結點
從未匹配點走未匹配邊到達另一個未匹配頂點也算增廣路
增廣路的作用:增廣路上的未匹配邊比匹配邊多一條 未匹配結點數爲2
如果將增廣路上的非匹配邊改爲匹配邊 匹配邊改爲非匹配邊 則匹配數+1
匈牙利算法就是不斷的找增廣路 當找不到增廣路的時候 則求出最大匹配
實現細節
1.通過dfs實現匈牙利算法較爲簡潔 因爲遞歸的形勢更方便找增廣路並修改邊
2.找增廣路的時候只要從一側的點集裏取點做起點即可
3.如果從一個點A出發,沒有找到增廣路徑,那麼無論再從別的點出發找到多少增廣路徑來改變現在的匹配,從A出發都永遠找不到增廣路徑 同樣如果從a出發找到了增廣路也不用再次從a找增廣路了
4.由2,3知 匈牙利算法只要將二分圖單側的結點一次dfs找一次增廣路即可
5.實際上二分圖匹配是一個網絡流問題(你們過兩天會學)
6.時間效率(基於鄰接表)由上面可知 一側的點進行dfs 每次dfs最壞將所有邊遍歷一遍 所以時間複雜度大概是m*n
7.基於bfs的匈牙利算法在稀疏圖中速度有優勢 但實現較爲複雜(常數級別的差距)
給出
For u in 左側結點{
Dfs(u)
}
Dfs(u){
For each(u,v){
找增廣路
}
}
這份僞代碼比較模糊 如果正確理解了匈牙利算法的思想相信不難實現
代碼實現:
/**************************************************** 二分圖匹配(匈牙利算法的DFS實現) by andyc_03 INIT:g[][]兩邊定點劃分的情況 CALL:res=hungary();輸出最大匹配數 優點:適於稠密圖,DFS找增廣路快,實現簡潔易於理解 時間複雜度:O(VE); ****************************************************/ const int MAXN=1000; int uN,vN; //u,v數目 int g[MAXN][MAXN];//編號是0~n-1的 int linker[MAXN]; bool used[MAXN]; bool dfs(int u) { int v; for(v=0;v<vN;v++) if(g[u][v]&&!used[v]) { used[v]=true; if(linker[v]==-1||dfs(linker[v])) { linker[v]=u; return true; } } return false; } int hungary() { int res=0; int u; memset(linker,-1,sizeof(linker)); for(u=0;u<uN;u++) { memset(used,0,sizeof(used)); if(dfs(u)) res++; } return res; }
簡單例子:
HDU 2063過山車
#include<stdio.h>
#include<string.h>
const int MAXN=510;
int uN,vN; //u,v數目
int g[MAXN][MAXN];//編號是0~n-1的
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)
{
int v;
for(v=1;v<=vN;v++)
if(g[u][v]&&!used[v])
{
used[v]=true;
if(linker[v]==-1||dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
return false;
}
int hungary()
{
int res=0;
int u;
memset(linker,-1,sizeof(linker));
for(u=1;u<=uN;u++)
{
memset(used,0,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
int main()
{
int k;
int u,v;
while(scanf("%d",&k),k)
{
scanf("%d%d",&uN,&vN);
memset(g,0,sizeof(g));
while(k--)
{
scanf("%d%d",&u,&v);
g[u][v]=1;
}
printf("%d\n",hungary());
}
return 0;
}
例:HDU 1045 Fire Net
/*
HDU 1045
*/
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
int uN,vN;
int g[20][20];
int linker[20];
bool used[20];
char map[5][5];
int mapr[5][5];
int mapl[5][5];
bool dfs(int u)
{
int v;
for(v=1;v<=vN;v++)
if(g[u][v]&&!used[v])
{
used[v]=true;
if(linker[v]==-1||dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
return false;
}
int hungary()
{
int res=0;
int u;
memset(linker,-1,sizeof(linker));
for(u=1;u<=uN;u++)
{
memset(used,0,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
int main()
{
int i,j,n;
while(scanf("%d",&n),n)
{
memset(mapl,0,sizeof(mapl));
memset(mapr,0,sizeof(mapr));
memset(g,0,sizeof(g));
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
cin>>map[i][j];
if(map[i][j]=='X')
mapl[i][j]=mapr[i][j]=-1;
}
int p1=0;
uN=0;vN=0;
//給行編號
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
while(mapr[i][j]==-1&&j<=n)
j++;
p1++;
while(mapr[i][j]!=-1&&j<=n)
{
mapr[i][j]=p1;
if(uN<p1) uN=p1;
j++;
}
}
int p2=0;
//給列編號
for(j=1;j<=n;j++)
for(i=1;i<=n;i++)
{
while(mapl[i][j]==-1&&i<=n)
i++;
p2++;
while(mapl[i][j]!=-1&&i<=n)
{
mapl[i][j]=p2;
if(vN<p2) vN=p2;
i++;
}
}
//建圖
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(mapr[i][j]!=-1&&mapl[i][j]!=-1)
g[mapr[i][j]][mapl[i][j]]=1;
}
printf("%d\n",hungary());
}
return 0;
}