二分圖;php
大意: web
二分圖指的是這樣一種圖,其全部頂點能夠分紅兩個集合X和Y,其中X或Y中任意兩個在同一集合中的點都不相連,全部的邊關聯在兩個頂點中,剛好一個屬於集合X,另外一個屬於集合Y。給定一個二分圖G,M爲G邊集的一個子集,若是M知足當中的任意兩條邊都不依附於同一個頂點,則稱M是一個匹配。圖中包含邊數最多的匹配稱爲圖的最大匹配。算法
二分圖的最大匹配有兩種求法,第一種是最大流;第二種就是我如今要講的匈牙利算法。這個算法說白了就是最大流的算法,可是它跟據二分圖匹配這個問題的特色,把最大流算法作了簡化,提升了效率。網絡
增廣路徑的定義(也稱增廣軌或交錯軌):spa
一條增廣路徑(Augmenting Path)是指從 M 中沒有用到的頂點開始,並從 M 中沒有用到的頂點結束的交替路徑。(PS:一條交替路徑(Alternating Path)是指這樣一條路徑,其中的每一條邊交替地屬於或不屬於匹配 M。好比說,第1、3、五條邊屬於 M,而第2、4、六條不屬於 M,等等。)orm
因此以下圖(3)中所示即爲一條增廣路徑。遊戲
結合增廣路徑的定義和下圖所示,咱們能夠理解如下結論:get
增廣路徑的長度一定爲奇數,第一條邊和最後一條邊都不屬於 M。it
將 M 和增廣路徑進行異或操做(去同存異)能夠獲得一個更大的匹配 M'。io
M' 比 M 的匹配數多 1。
M 爲 G 的最大匹配當且僅當不存在 M 的增廣路徑。
最大流算法的核心問題就是找增廣路徑(augment path)。匈牙利算法也不例外,它的基本模式就是:
初始時最大匹配爲空
while 找獲得增廣路徑
do 把增廣路徑加入到最大匹配中去
可見和最大流算法是同樣的。可是這裏的增廣路徑就有它必定的特殊性。(注:匈牙利算法雖然根本上是最大流算法,可是它不須要建網絡模型,因此圖中再也不須要源點和匯點,僅僅是一個二分圖。每條邊也不須要有方向。)
算法的思路是不停的找增廣路徑, 並增長匹配的個數,增廣路徑顧名思義是指一條可使匹配數變多的路徑,在匹配問題中,增廣路徑的表現形式是一條"交錯路徑",也就是說這條由圖的邊組成的路徑, 它的第一條邊是目前尚未參與匹配的,第二條邊參與了匹配,第三條邊沒有..最後一條邊沒有參與匹配,而且始點和終點尚未被選擇過。這樣交錯進行,顯然他有奇數條邊。那麼對於這樣一條路徑,咱們能夠將第一條邊改成已匹配,第二條邊改成未匹配...以此類推。也就是將全部的邊進行"反色",容易發現這樣修改之後,匹配仍然是合法的,可是匹配數增長了一對。另外,單獨的一條鏈接兩個未匹配點的邊顯然也是交錯路徑。能夠證實。當不能再找到增廣路徑時,就獲得了一個最大匹配,這也就是匈牙利算法的思路。
3個重要結論:
1 最小點覆蓋數: 最小覆蓋要求用最少的點(X集合或Y集合的都行)讓每條邊都至少和其中一個點關聯。能夠證實:最少的點(即覆蓋數)=最大匹配數
2 最小路徑覆蓋=最小路徑覆蓋=|N|-最大匹配數
用盡可能少的不相交簡單路徑覆蓋有向無環圖G的全部結點。解決此類問題能夠創建一個二分圖模型。把全部頂點i拆成兩個:X結點集中的i和Y結點集中的i',若是有邊i->j,則在二分圖中引入邊i->j',設二分圖最大匹配爲m,則結果就是n-m。
3 二分圖最大獨立集=頂點數-二分圖最大匹配
在N個點的圖G中選出m個點,使這m個點兩兩之間沒有邊,求m最大值。
若是圖G知足二分圖條件,則能夠用二分圖匹配來作.最大獨立集點數 = N - 最大匹配數。
例題:
http://acm.hdu.edu.cn/showproblem.php?pid=1281棋盤遊戲
一種建圖的方式,對於一個座標,x , y 能夠分紅兩個不一樣的集合,若是該點知足某種性質的話,就在 x , y 上連一條線,本題就是這樣的……
本題要求關鍵點,那麼只須要對於每一個可行點進行刪點,而後看看得出的最大匹配是否小於不刪點的解,若是小於,則是關鍵點……統計一下便可。
代碼:
[cpp] view plaincopy
/*==================================================*\
| 二分圖匹配(匈牙利算法DFS 實現)
| INIT: g[][]鄰接矩陣;
| 優勢:實現簡潔容易理解,適用於稠密圖,DFS找增廣路快。
| 找一條增廣路的複雜度爲O(E),最多找V條增廣路,故時間複雜度爲O(VE)
==================================================*/
#include<stdio.h>
#include<memory.h>
bool g[101][101]; //鄰接矩陣,true表明有邊相連
bool visit[101]; //記錄V2中的某個點是否被搜索過
int match[101]; //記錄與V2中的點匹配的點的編號
int n,m,k; //二分圖中左邊、右邊集合中頂點的數目
// 匈牙利算法
bool dfs(int u)
{
for (int i = 1; i <= m; ++i)
{
if (g[u][i] && !visit[i]) //若是節點i與u相鄰而且未被查找過
{
visit[i] = true; //標記i爲已查找過
if (match[i] == -1 || dfs(match[i])) //若是i未在前一個匹配M中,或者i在匹配M中,可是從與i相鄰的節點出發能夠有增廣路徑
{
match[i] = u; //記錄查找成功記錄,更新匹配M(即「取反」)
return true; //返回查找成功
}
}
}
return false;
}
inline int MaxMatch()
{
int i,sum=0;
memset(match,-1,sizeof(match));
for(i = 1 ; i <= n ; ++i)
{
memset(visit,false,sizeof(visit)); //清空上次搜索時的標記
if( dfs(i) ) //從節點i嘗試擴展
{
sum++;
}
}
return sum;
}
int main(void)
{
int i,j,ans,x,y,num,t=1;
while (scanf("%d %d %d",&n,&m,&k)!=EOF)
{
memset(g,false,sizeof(g)); //初始化
for (i = 1; i <= k; ++i)
{
scanf("%d %d",&x,&y);
g[x][y] = true;
}
ans = MaxMatch();
num = 0;
for (i = 1; i <= n; ++i)
{
for (j = 1; j <= m; ++j)
{
if(g[i][j] == true)
{
g[i][j] = false;
if(MaxMatch() < ans)
num++;
g[i][j] = true;
}
}
}
printf("Board %d have %d important blanks for %d chessmen.\n",t++,num,ans);
}
return 0;
}
ps:對於二分圖方面的題 難的不是代碼 而是你是否能看出這是一道二分圖的題 是否有這種轉化能力 固然 若是你知道了 套個模板基本就能對