KM算法原理+證實


title: KM算法原理+證實
date: 2020-04-26
categories: ["算法"]
summary: "以匈牙利算法爲基礎,改善後用於求解帶權二分圖的求最佳匹配問題。百度百科中有KM算法的介紹,當中有證實過程:[百度KM算法]"
author: White Song
tags: ["二分圖"]
cover: https://img.yilon.top/blog/czsh9.png
blog: https://blog.yilon.topios


介紹

匈牙利算法爲基礎,改善後用於求解帶權二分圖的求最佳匹配問題c++

百度百科中有KM算法的介紹,當中有證實過程:百度KM算法算法

最大權匹配

帶權二分圖的邊權重和最大匹配,以下圖所示,最大和爲102spa

img

最佳匹配

帶權二分圖的邊權重和最大完備匹配,以下圖所示.net

img

顯然,最佳匹配和最大權匹配並不一致,但若是把剩下的邊補上,而且設置權重爲零,那麼兩者能夠統一塊兒來3d

img

KM算法

下面講的KM算法是用來求最佳匹配,而非最大權匹配。code

KM算法是一種計算機算法,功能是求完備匹配下的最大權匹配。blog

若是不存在完備匹配,那麼算法會求最大匹配,若是最大匹配有多種,那麼結果是最大匹配中權重和最大的。遞歸

在一個二分圖內,左頂點爲X,右頂點爲Y,現對於每組左右鏈接XiYj有權,求最大匹配,而且使得該匹配中全部的和是最大。get

該算法是經過給每一個頂點一個標號(叫作頂標)來把求最大權匹配的問題轉化爲求完備匹配的問題的。

設頂點的頂標爲的頂標爲,頂點的頂標爲,頂點與之間的邊權爲,在算法執行過程當中,對於圖中的任意一條邊,始終成立。

相等子圖

G當中每一條邊有左右兩個頂標,*相等子圖*就是那些頂標和等於邊權重的邊構成的子圖,以下圖綠色加粗線構成相等子圖

img

KM算法的正確性基於如下的定理:

定理:若二分圖中,,而且存在某個相等子圖有完備匹配,那麼這個完備匹配就是二分圖的最大權匹配。

證實:由於這個完備匹配存在於相等子圖中,所以,這個匹配全部邊都知足:,同時因爲完備匹配包含全部的頂點,所以這個屬於相等子圖的完備匹配的總權重等於全部頂標的和。

若是這個二分圖存在另一個完備匹配,若是它不徹底屬於相等子圖,即存在某條邊,那麼該匹配的權重和就小於全部頂標的和,即小於上述屬於相等子圖的完備匹配的權重和。

算法過程

首先選擇頂點數較少的爲X部,初始時對X部的每個頂點設置頂標,頂標的值爲該點關聯的最大邊的權值,Y部的頂點頂標爲0。

img

對於X部中的每一個頂點,在相等子圖中利用匈牙利算法找一條增廣路徑,若是沒有找到,則修改頂標,擴大相等子圖,繼續找增廣路徑。當每一個點都找到增廣路徑時,此時意味着每一個點都在匹配中,即找到了二分圖的完備匹配。該完備匹配是最大權重的完備匹配,即爲二分圖的最佳匹配。

匈牙利算法對左邊第一個頂點,在相等子圖中進行增廣路徑搜索,找到路徑1-C後進行匹配增廣操做,以下圖所示

img

回答第一個問題,須要理解如何在保持相等子圖原來的邊符合相等子圖要求的同時,讓新加的邊也知足相等子圖的要求。

那麼在增廣路徑搜索時,咱們知道,若是下面這些紫色邊任意一條加入相等子圖後,均可以在相等子圖中使用匈牙利算法找到一條增廣路徑2-A(or 2-B or 2-C-1-A):

img

KM算法選擇上述三條紫色邊中,頂標和與邊權重差值最小的邊1-A或者2-A,以該最小差值爲d

這裏有一個問題就是爲何最小那個?首先比這更小了就不能擴大相等子圖了,可是若是大了,就不能保證A[i]+B[j]\geq w[i][j]老是成立了。好比上圖選擇了2-B邊的差值2,那麼修改頂標值後,1-A有A[i]+B[j]=2+0<3= w[i][j]

KM算法中須要修改的頂標,是那些在匈牙利算法增廣路徑搜索時,產生一棵交錯樹,爲了保證A[i]+B[j]\geq w[i][j]老是成立,交錯樹上全部的頂標都要參與修改。

好比在如上第二個頂點搜索增廣路徑時,產生以下圖所示的橙色頂標集合{1,2,C}:

img

修改頂標後產生以下圖所示的結果:

img

在該相等子圖上以頂點2爲開始點,搜索增廣路徑2-A(or 2-C-1-A),進行增廣操做:

img

一樣對左邊第三個點:

img

另一個問題是爲何修改橙色頂標而不去修改頂標A(找到最小差對應的邊的右邊頂標)?修改頂標A的值爲-1,那麼邊1-A也能夠加入相等子圖了。問題就在於能不能保證老是成立,以下圖所示結果,修改頂標A,邊3-A就不知足該條件了。除非在修改頂標A的同時,增長頂標3的值,可是須要修改的頂標集合須要額外的搜索算法,而修改橙色頂標所須要的交錯樹在增廣路徑搜索時能夠一併產生。

img

C++代碼

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<map>
#include<stdlib.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 3;
const int INF = 0x3f3f3f3f;
 
int wx[maxn], wy[maxn];//每一個點的頂標值(須要根據二分圖處理出來)
int cx[maxn], cy[maxn];//每一個點所匹配的點
int visx[maxn], visy[maxn];//每一個點是否加入增廣路
int cntx=maxn, cnty=maxn;//分別是X和Y的點數
//int Map[maxn][maxn] = { {3,INF,100},{2,1,3 },{INF,INF,5} };//二分圖邊的權值
int Map[maxn][maxn] = { {3,0,100},{2,1,3 },{0,0,5} };//二分圖邊的權值
int minz;//邊權和頂標最小的差值
 
bool dfs(int u)//進入DFS的都是X部的點
{
	visx[u] = 1;//標記進入增廣路
	for (int v = 0; v < cnty; v++)
	{
		if (!visy[v] && Map[u][v] != INF)//若是Y部的點還沒進入增廣路,而且存在路徑
		{
			int t = wx[u] + wy[v] - Map[u][v];
			if (t == 0)//t爲0說明是相等子圖
			{
				visy[v] = 1;//加入增廣路
							//若是Y部的點還未進行匹配
							//或者已經進行了匹配,可是能夠從v點原來匹配的cy[v]找到一條增廣路
							//由於visy[v] = 1;所以從if (!visy[v] && Map[u][v] != INF) 中能夠看出下一個找到的cy[v]-v'鏈接確定不在已經匹配的邊集合M中
							//由於當v'=v時if不知足!visy[v]=true
							//這保證了所找到的路徑是屬於M和不屬於M的邊交替出現
							//這時,下面遞歸的cx[u] = v; 和cy[v] =u 就能夠對路徑上的邊取反,達到增廣的效果
							//這是DFS的算法
							//那就能夠進行匹配
				if (cy[v] == -1 || dfs(cy[v]))
				{
					cx[u] = v;
					cy[v] = u;//進行匹配
					return true;
				}
			}
			else if (t > 0)//此處t必定是大於0,由於頂標之和必定>=邊權
			{
				minz = min(minz, t);//邊權和頂標最小的差值
			}
		}
	}
	return false;
}
 
int KM()
{
	memset(cx, -1, sizeof(cx));
	memset(cy, -1, sizeof(cy));
	memset(wx, 0, sizeof(wx));//wx的頂標爲該點鏈接的邊的最大權值
	memset(wy, 0, sizeof(wy));//wy的頂標爲0
	for (int i = 0; i < cntx; i++)//預處理出頂標值
	{
		for (int j = 0; j < cnty; j++)
		{
			if (Map[i][j] == INF) {
				continue;
			}
			wx[i] = max(wx[i], Map[i][j]);
		}
	}
	for (int i = 0; i < cntx; i++)//枚舉X部的點
	{
		while (1)
		{
			minz = INF;
			memset(visx, 0, sizeof(visx));
			memset(visy, 0, sizeof(visy));
			if (dfs(i))break;//已經匹配正確
 
							 //還未匹配,將X部的頂標減去minz,Y部的頂標加上minz
			for (int j = 0; j < cntx; j++)
				if (visx[j])wx[j] -= minz;
			for (int j = 0; j < cnty; j++)
				if (visy[j])wy[j] += minz;
		}
	}
 
	int ans = 0;//二分圖最優匹配權值
	for (int i = 0; i < cntx; i++)
		if (cx[i] != -1)ans += Map[i][cx[i]];
	return ans;
}
int n, k;
int main()
{
	int result = KM();
	return 0;
}
相關文章
相關標籤/搜索