1. 程式人生 > >KM演算法求帶權二分圖的最大匹配(完備匹配)

KM演算法求帶權二分圖的最大匹配(完備匹配)

1.基礎知識普及

二分圖的概念

二分圖又稱作二部圖,是圖論中的一種特殊 模型。 設G=(V,{R})是一個無向圖。如頂點集V可分 割為兩個互不相交的子集,並且圖中每條邊 依附的兩個頂點都分屬兩個不同的子集。則圖G成為二分圖。
通俗來講,二分圖指的是這樣一種圖:其所有的頂點分成兩個集合M和N,其中M或N中任意兩個在同一集合中的點都不相連。

二分圖匹配

是指求出一組邊,其中的頂點分別在兩個集合中,並且任意兩條邊都沒有相同的頂點,這組邊叫做二分圖的匹配,所有的匹配中,邊數最多、權值之和最大的那個匹配,叫做最大匹配。

完全匹配

如果一個匹配中,圖中的每一個頂點都和圖 中某條邊相關聯,則稱此匹配為完全匹配 完全匹配, 完全匹配 也稱作完備匹配。

2.基本演算法

二分圖如果是沒有權值的,求最大匹配。則是用匈牙利演算法求最大匹配。如果帶了權值,求最大或者最小權匹配,則必須用KM演算法。

其實最大和最小權匹配都是一樣的問題。只要會求最大匹配,如果要求最小權匹配,則將權值取相反數,再把結果取相反數,那麼最小權匹配就求出來了。

2.1 匈牙利演算法

2.2 KM演算法

KM演算法用來解決最大權匹配問題: 在一個二分圖內,左頂點為X,右頂點為Y,現對於每組左右連線XiYj有權wij,求一種匹配使得所有wij的和最大。也就是最大權匹配一定是完備匹配。如果兩邊的點數相等則是完美匹配。如果點數不相等,其實可以虛擬一些點,使得點數相等,也成為了完美匹配。最大權匹配還可以用最大流去解決

Kuhn-Munkras演算法流程:

  (1)初始化可行頂標的值

  (2)用匈牙利演算法尋找完備匹配

  (3)若未找到完備匹配則修改可行頂標的值

  (4)重複(2)(3)直到找到相等子圖的完備匹配為止 

// GY0218.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"


#include <iostream>  
#include <cstdio>  
#include <memory.h>  
#include <algorithm>   

using namespace std;  

#define MAX 100  
int n; int weight[MAX][MAX]; //權重 int lx[MAX],ly[MAX]; //定點標號 bool sx[MAX],sy[MAX]; //記錄尋找增廣路時點集x,y裡的點是否搜尋過 int match[MAX]; //match[i]記錄y[i]與x[match[i]]相對應 bool search_path(int u) { //給x[u]找匹配,這個過程和匈牙利匹配是一樣的 sx[u]=true; for(int v=0; v<n; v++){ if(!sy[v] &&lx[u]+ly[v] == weight[u][v]){ sy[v]=true; if(match[v]==-1 || search_path(match[v])){ match[v]=u; return true; } } } return false; } int Kuhn_Munkras(bool max_weight){ if(!max_weight){ //如果求最小匹配,則要將邊權取反 for(int i=0;i<n;i++) for(int j=0;j<n;j++) weight[i][j]=-weight[i][j]; } //初始化頂標,lx[i]設定為max(weight[i][j] | j=0,..,n-1 ), ly[i]=0; for(int i=0;i<n;i++){ ly[i]=0; lx[i]=-0x7fffffff; for(int j=0;j<n;j++) if(lx[i]<weight[i][j]) lx[i]=weight[i][j]; } memset(match,-1,sizeof(match)); //不斷修改頂標,直到找到完備匹配或完美匹配 for(int u=0;u<n;u++){ //為x裡的每一個點找匹配 while(1){ memset(sx,0,sizeof(sx)); memset(sy,0,sizeof(sy)); if(search_path(u)) //x[u]在相等子圖找到了匹配,繼續為下一個點找匹配 break; //如果在相等子圖裡沒有找到匹配,就修改頂標,直到找到匹配為止 //首先找到修改頂標時的增量inc, min(lx[i] + ly [i] - weight[i][j],inc);,lx[i]為搜尋過的點,ly[i]是未搜尋過的點,因為現在是要給u找匹配,所以只需要修改找的過程中搜索過的點,增加有可能對u有幫助的邊 int inc=0x7fffffff; for(int i=0;i<n;i++) if(sx[i]) for(int j=0;j<n;j++) if(!sy[j]&&((lx[i] + ly [j] - weight[i][j] )<inc)) inc = lx[i] + ly [j] - weight[i][j] ; //找到增量後修改頂標,因為sx[i]與sy[j]都為真,則必然符合lx[i] + ly [j] =weight[i][j],然後將lx[i]減inc,ly[j]加inc不會改變等式,但是原來lx[i] + ly [j] !=weight[i][j]即sx[i]與sy[j]最多一個為真,lx[i] + ly [j] 就會發生改變,從而符合等式,邊也就加入到相等子圖中 if(inc==0) cout<<"fuck!"<<endl; for(int i=0;i<n;i++){ if(sx[i]) // lx[i]-=inc; if(sy[i]) ly[i]+=inc; } } } int sum=0; for(int i=0;i<n;i++) if(match[i]>=0) sum+=weight[match[i]][i]; if(!max_weight) sum=-sum; return sum; } int main(){ scanf("%d",&n); for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",&weight[i][j]); printf("%d\n",Kuhn_Munkras(1)); system("pause"); return 0; }