KM演算法求帶權二分圖的最大匹配(完備匹配)
阿新 • • 發佈:2018-12-24
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;
}