匈牙利算法


二分圖的最大匹配:匈牙利算法

講之前本蒟蒻先普及一個重要專業名詞

增廣路。

如果你仔細讀過並畫過圖,不難發現如果找到一條增廣路,那么配對的個數就會加1。
所以說,增廣路的本質其實就是一條路徑的起點和終點都未配對的點的邊。


匈牙利算法:

這個叫匈牙利算法(Hungarian method)的東西是由匈牙利數學家Edmonds於1965年提出,所以叫匈牙利算法。匈牙利算法是二分圖匹配最常見的算法,該算法的核心就是尋找增廣路徑,它是一種用增廣路徑求二分圖最大匹配的算法。

復雜度:

時間復雜度 :

鄰接矩陣最壞為 $ O( n^3 ) $
鄰接表:$ O(mn) $

空間復雜度 :

鄰接矩陣:$ O( n^2 ) $
鄰接表:$ O(n+m) $

另一個重要概念:二分圖

二分圖是圖論中的一種特殊模型。 設G=(V,E)是一個無向圖,如果頂點V可分割為兩個互不相交的子集(A,B),並且圖中的每條邊(i,j)所關聯的兩個頂點i和j分別屬於這兩個不同的頂點集,則稱圖G為一個二分圖。
學過高中數學的話應該能看懂我在說什么(逃

簡而言之,就是頂點集V可分割為兩個互不相交的子集,並且圖中每條邊依附的兩個頂點都分屬於這兩個互不相交的子集,兩個子集內的頂點不相鄰。滿足這樣的圖就叫二分圖。

但我們怎么判斷一個圖是不是二分圖???

其實也不難,用紅藍點的方法就行。首先講任意的一個頂點染成紅色,再把這個點相鄰的頂點染成藍色,如果按照這種染色方式可以將所有的頂點全部着色,並且相鄰的頂點的顏色不同,那么該圖就是一個二分圖。

這里貼一下代碼
#define MAXV 1000//這里應該根據題目自定

vector<int> G[MAXV];  //圖 
int V;                       //頂點數 
int color[MAXV];  //頂點的顏色 (1 or -1) 


//頂點v,顏色c 
bool dfs(int v,int c){
    color[v] = c;
    //把當前頂點相鄰的頂點掃一遍 
    for(int i = 0;i < G[v].size(); i++){
        //如果相鄰頂點已經被染成同色了,說明不是二分圖 
        if(color[G[v][i]] == c) return false;
        //如果相鄰頂點沒有被染色,染成-c,看相鄰頂點是否滿足要求 
        if(color[G[v][i]] == 0 && !dfs(G[v][i],-c)) return false;
    }
    //如果都沒問題,說明當前頂點能訪問到的頂點可以形成二分圖 
    return true;
}

void solve(){
    //可能是不連通圖,所以每個頂點都要dfs一次 
    for(int i = 0;i < V; i++){
        if(color[i] == 0){
            //第一個點顏色為 1 
            if(!dfs(i,1)){
                cout << "No" << endl;
                return;
            }
        }
    }
}

這才是正文!!!

既然上面本蒟蒻已經介紹完了有關二分圖的知識,那下面該講下匈牙利算法了!!!

根據上文的描述,既然增廣路的作用是“改進匹配方案”(即增加配對數),那么如果我們已經找到了一種匹配方案,不難發現如果在當前匹配方案下再也找不到任何增廣路的話,那么當前匹配就是二分圖的最大匹配,算法如下。

1.首先從任意的一個未配對的點u開始,從點u的邊中任意選一條邊(假設這條邊是從 $ u->v $ )開始配對。如果點v未配對,則配對成功,這是便找到了一條增廣路。如果點v已經被配對,就去嘗試“連鎖反應”,如果這時嘗試成功,就更新原來的配對關系。
所以這里要用一個 $ matched[v] = u $ 。配對成功就將配對數加1,。

2.如果剛才所選的邊配對失敗,那就要從點u的邊中重新選一條邊重新去試。直到點u
配對成功,或嘗試過點u的所有邊為止。

3.接下來就繼續對剩下的未配對過的點一一進行配對,直到所有的點都已經嘗試完畢,找不到新的增廣路為止。

4.輸出配對數。

CODE:

//如果你已經讀完題,請自動從int main()處開始閱讀 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>

#define N 2010

using namespace std;

int n,m,e,ans;
int vis[N][N];
int ask[N],matched[N];

inline bool found(int x){ //dfs找增廣路 
    for (int i = 1 ; i <= m ; i++)
      if (vis[x][i]){
        if (ask[i]) 
			continue;
        ask[i] = 1;
        if (!matched[i] || found(matched[i])) { 
			matched[i] = x ; 
			return true;
		}
      }
    return false;
}

inline void match(){
    int cnt = 0;//cnt是計數器 
    memset(matched,0,sizeof(matched));
    for (int i = 1 ; i <= n ; i++){
      memset(ask,0,sizeof(ask));
      if (found(i)) 
	  	cnt++;	//找到了就加1 
    }
    ans = cnt;
}
//從這里向下看起 
int main(){
    scanf("%d%d%d",&n,&m,&e);//結點個數分別為n,m,邊數為e
    for (int i = 1 ; i <= e ; i++){
      int x,y;
      scanf("%d%d",&x,&y);
      vis[x][y] = 1;
    }
    match();///匈牙利算法,見上 
    printf("%d \n",ans);
    return 0;
}

再補一個臨接表的實現(就只放函數)

bool dfs(int x) {
	for(int i = head[x] ; i ; i = e[i].to) {
		int v = e[i].from;
		if(!used[v]) {
			used[v] = 1;
			if(!matched[v] || dfs(matched[i]) ) {
				matched[v] = u;
				return 1;
			}
		}
	}
	return 0;
}
int match() {
	int res = 0;    
	memset(match,-1,sizeof(match) );    
	for(int u = 0 ; u < uN ; u++) {        
		memset(used, -1 , sizeof(used));        
		if(dfs(u)) res++;    
	}    
	return res; 
}

完結撒花(逃!!!


免責聲明!

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



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