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
帶權二分圖的邊權重和最大的完備匹配,以下圖所示.net
顯然,最佳匹配和最大權匹配並不一致,但若是把剩下的邊補上,而且設置權重爲零,那麼兩者能夠統一塊兒來3d
下面講的KM算法是用來求最佳匹配,而非最大權匹配。code
KM算法是一種計算機算法,功能是求完備匹配下的最大權匹配。blog
若是不存在完備匹配,那麼算法會求最大匹配,若是最大匹配有多種,那麼結果是最大匹配中權重和最大的。遞歸
在一個二分圖內,左頂點爲X,右頂點爲Y,現對於每組左右鏈接XiYj有權,求最大匹配,而且使得該匹配中全部的和是最大。get
該算法是經過給每一個頂點一個標號(叫作頂標)來把求最大權匹配的問題轉化爲求完備匹配的問題的。
設頂點的頂標爲的頂標爲,頂點的頂標爲,頂點與之間的邊權爲,在算法執行過程當中,對於圖中的任意一條邊,始終成立。
G當中每一條邊有左右兩個頂標,*相等子圖*就是那些頂標和等於邊權重的邊構成的子圖,以下圖綠色加粗線構成相等子圖
KM算法的正確性基於如下的定理:
定理
:若二分圖中,,而且存在某個相等子圖有完備匹配,那麼這個完備匹配就是二分圖的最大權匹配。
證實:由於這個完備匹配存在於相等子圖中,所以,這個匹配全部邊都知足:,同時因爲完備匹配包含全部的頂點,所以這個屬於相等子圖的完備匹配的總權重等於全部頂標的和。
若是這個二分圖存在另一個完備匹配,若是它不徹底屬於相等子圖,即存在某條邊,那麼該匹配的權重和就小於全部頂標的和,即小於上述屬於相等子圖的完備匹配的權重和。
首先選擇頂點數較少的爲X部,初始時對X部的每個頂點設置頂標,頂標的值爲該點關聯的最大邊的權值,Y部的頂點頂標爲0。
對於X部中的每一個頂點,在相等子圖中利用匈牙利算法找一條增廣路徑,若是沒有找到,則修改頂標,擴大相等子圖,繼續找增廣路徑。當每一個點都找到增廣路徑時,此時意味着每一個點都在匹配中,即找到了二分圖的完備匹配。該完備匹配是最大權重的完備匹配,即爲二分圖的最佳匹配。
匈牙利算法對左邊第一個頂點,在相等子圖中進行增廣路徑搜索,找到路徑1-C後進行匹配增廣操做,以下圖所示
回答第一個問題,須要理解如何在保持相等子圖原來的邊符合相等子圖要求的同時,讓新加的邊也知足相等子圖的要求。
那麼在增廣路徑搜索時,咱們知道,若是下面這些紫色邊任意一條加入相等子圖後,均可以在相等子圖中使用匈牙利算法找到一條增廣路徑2-A(or 2-B or 2-C-1-A):
KM算法選擇上述三條紫色邊中,頂標和與邊權重差值最小的邊1-A或者2-A,以該最小差值爲d
這裏有一個問題就是爲何最小那個?首先比這更小了就不能擴大相等子圖了,可是若是大了,就不能保證老是成立了。好比上圖選擇了2-B邊的差值2,那麼修改頂標值後,1-A有。
而KM算法中須要修改的頂標,是那些在匈牙利算法增廣路徑搜索時,產生一棵交錯樹,爲了保證老是成立,交錯樹上全部的頂標都要參與修改。
好比在如上第二個頂點搜索增廣路徑時,產生以下圖所示的橙色頂標集合{1,2,C}:
修改頂標後產生以下圖所示的結果:
在該相等子圖上以頂點2爲開始點,搜索增廣路徑2-A(or 2-C-1-A),進行增廣操做:
一樣對左邊第三個點:
另一個問題是爲何修改橙色頂標而不去修改頂標A(找到最小差對應的邊的右邊頂標)?修改頂標A的值爲-1,那麼邊1-A也能夠加入相等子圖了。問題就在於能不能保證老是成立,以下圖所示結果,修改頂標A,邊3-A就不知足該條件了。除非在修改頂標A的同時,增長頂標3的值,可是須要修改的頂標集合須要額外的搜索算法,而修改橙色頂標所須要的交錯樹在增廣路徑搜索時能夠一併產生。
#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; }