KM算法學習筆記

二分圖定義

圖的頂點剛好能夠分紅兩個集合,同一個集合內的頂點間不容許有邊,處在不一樣集合的頂點容許有邊相連。html

問題分類

  • 最大匹配問題:匈牙利算法、Hopcroft–Karp算法
  • 最優權值匹配問題:Kuhn-Munkras算法

關鍵思想

增廣路(augmenting path):假設目前已有一個匹配結果,存在一組未匹配定點的OD,可以找到一條路徑,這條路徑上匹配和未匹配的定點交替出現,稱爲增廣路算法

增廣路上的匹配和未匹配取反,則匹配數增長1。數據結構

KM算法

基本思想:經過引入頂標,將最優權值匹配轉化爲最大匹配問題。函數

clipboard.png

步驟1:將邊權值轉化爲頂標/標杆,通常來說,初始化時,X集合的元素取對應權重的最大值,Y集合的元素取0。取出知足如下條件的邊,構建二分圖:weight(i,j) = label(i) + label(j);該二分圖稱爲相等子圖ui

clipboard.png

步驟2:尋找增廣路,從X0開始,找到X0Y4;在X1,找不到增廣路,須要調整頂標,擴大相等子圖;當找不到增廣路徑時,對於搜索過的路徑上的XY點,設該路徑上的X頂點集爲S,Y頂點集爲T,對全部在S中的點xi及不在T中的點yj,計算d=min{(L(xi)+L(yj)-weight(xiyj))},從S集中的X標杆中減去d,並將其加入到T集中的Y的標杆中;本例尋找增廣路的過程當中,訪問了X一、Y四、X0三個節點,所以對應的邊是X1Y0,d爲2(從貪心選邊的角度看,咱們能夠爲X0選擇新的邊而拋棄原先的二分子圖中的匹配邊,也能夠爲X1選擇新的邊而拋棄原先的二分子圖中的匹配邊,由於咱們不能同時選擇X0Y4和X1Y4,由於這是一個不合法匹配,這個時候,d=min{(L(xi)+L(yj)-weight(xiyj))}的意義就在於,咱們選擇一條新的邊,這條邊將被加入匹配子圖中使得匹配合法,選擇這條邊造成的匹配子圖,將比原先的匹配子圖加上這條非法邊組成的非法匹配子圖的權重和(若是它是合法的,它將是最大的)小最少,即權重最大了);此時再次爲X1尋找增廣路,獲得X1Y0.spa

clipboard.png

步驟3:對X2尋找增廣路,搜索範圍如上圖藍色路徑所示,找不到增廣路,須要擴大相等子圖;按照步驟2同一規則,會將邊X0Y二、X2Y1加入,d=1..net

clipboard.png

步驟4:在新的相等子圖上,對X2從新尋找增廣路。若是是深度優先,獲得的路線是X2Y0->Y0X1->X1Y4->Y4X0->X0Y2,此時將匹配結果取反,則獲得X2Y0、X1Y四、X0Y2三個匹配;若是是寬度優先,獲得的匹配結果是X0Y四、X1Y0、X2Y1,以下圖:code

clipboard.png

Python實現htm

import numpy as np

# 聲明數據結構
adj_matrix = build_graph() # np array with dimension N*N

# 初始化頂標
label_left = np.max(adj_matrix, axis=1)  # init label for the left set
label_right = np.zeros(N)  # init label for the right set

# 初始化匹配結果
match_right = np.empty(N) * np.nan

# 初始化輔助變量
visit_left = np.empty(N) * False
visit_right = np.empty(N) * False
slack_right = np.empty(N) * np.inf

# 尋找增廣路,深度優先
def find_path(i):
  visit_left[i] = True
  for j, match_weight in enumerate(adj_matrix[i]):
    if visit_right[j]: continue  # 已被匹配(解決遞歸中的衝突)
    gap = label_left[i] + label_right[j] - match_weight
    if gap == 0:
      # 找到可行匹配
      visit_right[j] = True
      if np.isnan(match_right[j]) or find_path(match_right[j]):  ## j未被匹配,或雖然j已被匹配,可是j的已匹配對象有其餘可選備胎
        match_right[j] = i
          return True
        else:
      # 計算變爲可行匹配須要的頂標改變量
      if slack_right[j] < gap: slack_right[j] = gap
     return False

# KM主函數
def KM():
  for i in range(N):
      # 重置輔助變量
      slack_right = np.empty(N) * np.inf
      while True:
        # 重置輔助變量
        visit_left = np.empty(N) * False
                visit_right = np.empty(N) * False
        
          # 能找到可行匹配
        if find_path(i):    break
          # 不能找到可行匹配,修改頂標
        # (1)將全部在增廣路中的X方點的label所有減去一個常數d 
        # (2)將全部在增廣路中的Y方點的label所有加上一個常數d
        d = np.inf
        for j, slack in enumerate(slack_right):
          if not visit_right[j] and slack < d:
            d = slack
        for k in range(N):
          if visit_left[k]: label_left[k] -= d
        for n in range(N):
          if visit_right[n]: label_right[n] += d
    res = 0
  for j in range(N):
    if match_right[j] >=0 and match_right[j] < N:
      res += adj_matrix[match[j]][j]
  return res

參考資料

http://blog.sina.com.cn/s/blo...對象

https://blog.csdn.net/mosquit...

相關文章
相關標籤/搜索