匈牙利算法(二分圖)


                                                                                                            ---------------------------------------------------------------------題材大多來自網絡,本篇由神犇整理

基本概念二分圖

二分圖:是圖論中的一種特殊模型。若能將無向圖G=(V,E)的頂點V划分為兩個交集為空的頂點集,並且任意邊的兩個端點都分屬於兩個集合,則稱圖G為一個為二分圖。

 

匹配:一個匹配即一個包含若干條邊的集合,且其中任意兩條邊沒有公共端點。如下圖,圖3的紅邊即為圖2的一個匹配。

 

1 最大匹配
    在G的一個子圖M中,M的邊集中的任意兩條邊都不依附於同一個頂點,則稱M是一個匹配。選擇這樣的邊數最大的子集稱為圖的最大匹配問題,最大匹配的邊數稱為最大匹配數.如果一個匹配中,圖中的每個頂點都和圖中某條邊相關聯,則稱此匹配為完全匹配,也稱作完備匹配。如果在左右兩邊加上源匯點后,圖G等價於一個網絡流,最大匹配問題可以轉為最大流的問題。解決此問的匈牙利算法的本質就是尋找最大流的增廣路徑。上圖中的最大匹配如下圖紅邊所示:

 

2 最優匹配

最優匹配又稱為帶權最大匹配,是指在帶有權值邊的二分圖中,求一個匹配使得匹配邊上的權值和最大。一般X和Y集合頂點個數相同,最優匹配也是一個完備匹配,即每個頂點都被匹配。如果個數不相等,可以通過補點加0邊實現轉化。一般使用KM算法解決該問題。

 

3 最小覆蓋

二分圖的最小覆蓋分為最小頂點覆蓋和最小路徑覆蓋:

①最小頂點覆蓋是指最少的頂點數使得二分圖G中的每條邊都至少與其中一個點相關聯,二分圖的最小頂點覆蓋數=二分圖的最大匹配數;

②最小路徑覆蓋也稱為最小邊覆蓋,是指用盡量少的不相交簡單路徑覆蓋二分圖中的所有頂點。二分圖的最小路徑覆蓋數=|V|-二分圖的最大匹配數;

 

4 最大獨立集

    最大獨立集是指尋找一個點集,使得其中任意兩點在圖中無對應邊。對於一般圖來說,最大獨立集是一個NP完全問題,對於二分圖來說最大獨立集=|V|-二分圖的最大匹配數。如下圖中黑色點即為一個最大獨立集:

 

基本概念匈牙利算法

交替路:從一個未匹配點出發,依次經過非匹配邊、匹配邊、非匹配邊...形成的路徑叫交替路。*

增廣路:從一個未匹配點出發,走交替路,如果途徑另一個未匹配點(出發的點不算),則這條交替路稱為增廣路(agumenting path)。

 

二、最大匹配與最小點覆蓋

最小點覆蓋:假如選了一個點就相當於覆蓋了以它為端點的所有邊,你需要選擇最少的點來覆蓋所有的邊

最小割定理是一個二分圖中很重要的定理:一個二分圖中的最大匹配數等於這個圖中的最小點覆蓋數。

 

最小點集覆蓋==最大匹配。在這里解釋一下原因,首先,最小點集覆蓋一定>=最大匹配,因為假設最大匹配為n,那么我們就得到了n條互不相鄰的邊,光覆蓋這些邊就要用到n個點。現在我們來思考為什么最小點擊覆蓋一定<=最大匹配。任何一種n個點的最小點擊覆蓋,一定可以轉化成一個n的最大匹配。因為最小點集覆蓋中的每個點都能找到至少一條只有一個端點在點集中的邊(如果找不到則說明該點所有的邊的另外一個端點都被覆蓋,所以該點則沒必要被覆蓋,和它在最小點集覆蓋中相矛盾),只要每個端點都選擇一個這樣的邊,就必然能轉化為一個匹配數與點集覆蓋的點數相等的匹配方案。所以最大匹配至少為最小點集覆蓋數,即最小點擊覆蓋一定<=最大匹配。綜上,二者相等。

 

三、匈牙利算法

由增廣路的性質,增廣路中的匹配邊總是比未匹配邊多一條,所以如果我們放棄一條增廣路中的匹配邊,選取未匹配邊作為匹配邊,則匹配的數量就會增加。匈牙利算法就是在不斷尋找增廣路,如果找不到增廣路,就說明達到了最大匹配。

先給一個例子 
1、起始沒有匹配 

2、選中第一個x點找第一跟連線 

 


3、選中第二個點找第二跟連線 

 


4、發現x3的第一條邊x3y1已經被人占了,找出x3出發的的交錯路徑x3-y1-x1-y4,把交錯路中已在匹配上的邊x1y1從匹配中去掉,剩余的邊x3y1 x1y4加到匹配中去 

 


5、同理加入x4,x5。 

匈牙利算法可以深度有限或者廣度優先,剛才的示例是深度優先,即x3找y1,y1已經有匹配,則找交錯路。若是廣度優先,應為:x3找y1,y1有匹配,x3找y2。

 

算法模板(鄰接表 & C++)

 

深度優先匈牙利算法代碼

#define maxn 10//表示x集合和y集合中頂點的最大個數!
 int nx,ny;//x集合和y集合中頂點的個數
 int edge[maxn][maxn];//edge[i][j]為1表示ij可以匹配
 int cx[maxn],cy[maxn];//用來記錄x集合中匹配的y元素是哪個!
 int visited[maxn];//用來記錄該頂點是否被訪問過!
 int path(int u)
 {
     int v;
     for(v=0;v<ny;v++)
     {
         if(edge[u][v]&&!visited[v])
         {
             visited[v]=1;
            if(cy[v]==-1||path(cy[v]))//如果y集合中的v元素沒有匹配或者是v已經匹配,但是從cy[v]中能夠找到一條增廣路
             {
                 cx[u]=v;
                 cy[v]=u;
                 return 1;
             }
         }
     }
     return 0;
 }
 int maxmatch()
 {
     int res=0;
     memset(cx,0xff,sizeof(cx));//初始值為-1表示兩個集合中都沒有匹配的元素!
     memset(cy,0xff,sizeof(cy));
     for(int i=0;i<=nx;i++)
     {
         if(cx[i]==-1)
         {
             memset(visited,0,sizeof(visitited));
             res+=path(i);
         }
     }
     return res;
 }

四、相關POJ題目

(1) poj3041

題目意思就是一顆子彈可以干掉任意一行或一列的障礙,問最少需要花費多少子彈清除呢

也就是求最小點集覆蓋。

#include<iostream>
#include<stdio.h>
#include<string.h>
#define Max 505
using namespace std;
int a[Max][Max];
int visit[Max];
int match[Max];
int N,K;
int path(int u)
{
    int v;
    for(v=1;v<=N;v++)
    {
          if(a[u][v] && !visit[v])
          {
                visit[v] = 1;
                if(match[v] == -1 || path(match[v]))
                {
                            match[v] = u;
                            return 1;
                }  
          }
    }
    return 0;
}
int main()
{
                                                                                                          
      int i,j,k,count;
      scanf("%d %d",&N,&K);
                                                                                                          
      memset(a,0,sizeof(a));
      memset(match,-1,sizeof(match));
      count = 0;
      for(i=1;i<=K;i++)
      {
           scanf("%d %d",&j,&k);
           a[j][k] = 1;
      }
                                                                                                            
      for(i=1;i<=N;i++)
      {
             memset(visit,0,sizeof(visit));
             if(path(i))
               count++;
      }
      printf("%d\n",count);
                                                                                                          
      return 0;
}

(2) poj3020

此題重要的是做出圖來,以什么樣的觀點作圖。可以畫 h*w -> h*w 的圖,點i與上下左右四個點有邊存在,求最大匹配,最后結果為 總點數 - 最大匹配/2

#include<iostream>
#include<stdio.h>
#include<string.h>
#define Max 520
using namespace std;
int a[Max][Max];
int visit[Max];
int match[Max];
int N;
char str[50][15];
int path(int u)
{
    int v;
    for(v=1;v<=N;v++)
    {
          if(a[u][v] && !visit[v])
          {
                visit[v] = 1;
                if(match[v] == -1 || path(match[v]))
                {
                            match[v] = u;
                            return 1;
                }  
          }
    }
    return 0;
}
int Find()
{
    int count = 0;
    int i;
    for(i=1;i<=N;i++)
    {
             memset(visit,0,sizeof(visit));
             if(path(i))
               count++;
    }
    return count;
}
int init(int h,int w) {
    int ctr,i,j;
    int x,y;
    int s,d;
    ctr=0;
    for ( i=1;i<=h;i++ ) {
        for ( j=1;j<=w;j++ ) {
            if ( str[i][j]=='*' ) {
                ctr++;
                x=i;
                y=j;
                s=(x-1)*w+y;
                if ( y+1<=w&&str[x][y+1]=='*' ) {
                    d=(x-1)*w+y+1;
                    a[s][d]=1;
                }
                if ( x+1<=h&&str[x+1][y]=='*' ) {
                    d=(x)*w+y;
                    a[s][d]=1;
                }
                if ( y-1>=1&&str[x][y-1]=='*' ) {
                    d=(x-1)*w+y-1;
                    a[s][d]=1;
                }
                if ( x-1>=1&&str[x-1][y]=='*' ) {
                    d=(x-2)*w+y;
                    a[s][d]=1;
                }
            }
        }
    }
    return ctr;
}
int main()
{
                            
      int i,j,k,n,totalnum,matchnum,h,w;
      scanf("%d",&n);
      while(n--)
      { 
          memset(a,0,sizeof(a));
          memset(match,-1,sizeof(match));
                                  
          scanf("%d %d",&h,&w);
          getchar();
          for(i=1;i<=h;i++)
          {
              for(j=1;j<=w;j++)
              {
                 scanf("%c",&str[i][j]);
              }
              getchar();
          }
                                  
          totalnum = init(h,w);
          N = h*w;
          matchnum = Find();
                                  
          printf("%d\n",totalnum - matchnum/2);
                                                 
                                  
      }
      return 0;
}//二分圖

 如果你還沒有看明白,再看看這里:http://blog.csdn.net/dark_scope/article/details/8880547

pass:個人覺得寫得不錯,就貼上了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM