二分圖的最大匹配

二分圖;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:對於二分圖方面的題  難的不是代碼 而是你是否能看出這是一道二分圖的題 是否有這種轉化能力  固然 若是你知道了 套個模板基本就能對

相關文章
相關標籤/搜索