二分圖,匈牙利算法

二分圖的基本知識:

  對於無向圖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;   

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章