算法筆記_139:二分圖的最大權匹配(Java)


目錄

1 問題描述

2 解決方案

 


1 問題描述

何為二分圖的最大權匹配問題?

最大權二分匹配問題就是給二分圖的每條邊一個權值,選擇若干不相交的邊,得到的總權值最大。

 

 


2 解決方案

對於此問題的講解,引用文末參考資料1

解決這個問題可以用KM算法。理解KM算法需要首先理解可行頂標的概念。可行頂標是指關於二分圖兩邊的每個點的一個值lx[i]ly[j],保證對於每條邊w[i][j]都有lx[i]+ly[j]-w[i][j]>=0。如果所有滿足lx[i]+ly[j]==w[i][j]的邊組成的導出子圖中存在一個完美匹配,那么這個完美匹配肯定就是原圖中的最大權匹配。理由很簡單:這個匹配的權值之和恰等於所有頂標的和,由於上面的那個不等式,另外的任何匹配方案的權值和都不會大於所有頂標的和。

但問題是,對於當前的頂標的導出子圖並不一定存在完美匹配。這時,可以用某種方法對頂標進行調整。調整的方法是:根據最后一次不成功的尋找交錯路的DFS,取所有i被訪問到而j沒被訪問到的邊(i,j)lx[i]+ly[j]-w[i][j]的最小值d。將交錯樹中的所有左端點的頂標減小d,右端點的頂標增加d。經過這樣的調整以后:原本在導出子圖里面的邊,兩邊的頂標都變了,不等式的等號仍然成立,仍然在導出子圖里面;原本不在導出子圖里面的邊,它的左端點的頂標減小了,右端點的頂標沒有變,而且由於d的定義,不等式仍然成立,所以他就可能進入了導出子圖里。

初始時隨便指定一個可行頂標,比如說lx[i]=max{w[i][j]|j是右邊的點}ly[i]=0。然后對每個頂點進行類似Hungary算法的find過程,如果某次find沒有成功,則按照這次find訪問到的點對可行頂標進行上述調整。這樣就可以逐步找到完美匹配了。

值得注意的一點是,按照上述d的定義去求d的話需要O(N^2)的時間,因為d需要被求O(N^2)次,這就成了算法的瓶頸。可以這樣優化:設slack[j]表示右邊的點j的所有不在導出子圖的邊對應的lx[i]+ly[j]-w[i][j]的最小值,在find過程中,若某條邊不在導出子圖中就用它對相應的slack值進行更新。然后求d只要用O(N)的時間找到slack中的最小值就可以了。

 

下面代碼所使用的測試數據如下圖:

 

 

具體代碼如下:

package com.liuzhen.practice;

import java.util.Scanner;

public class Main {
    public static int MAX = 100;
    public static int n;
    public static int[][] value = new int[MAX][MAX];   //給定二分圖的權重值
    public static int[] lx = new int[MAX];   //記錄二分圖左半部分頂點的可行頂標
    public static int[] ly = new int[MAX];   //記錄二分圖右半部分頂點的可行頂標
    public static boolean[] sx = new boolean[MAX];//用於記錄二分圖左半部分頂點是否在最終結果中
    public static boolean[] sy = new boolean[MAX];//用於記錄二分圖右半部分頂點是否在最終結果中
    public static int[] pre = new int[MAX];  //用於記錄最終結果中頂點y匹配的頂點x
    
    public boolean dfs(int x) {   //采用匈牙利算法找增廣路徑
        sx[x] = true;       //代表左半部分頂點x包含在最終結果中
        for(int y = 0;y < n;y++) {
            if(!sy[y] && lx[x] + ly[y] == value[x][y]) {
                sy[y] = true;   //代表右半部分頂點y包含在最終結果中
                if(pre[y] == -1 || dfs(pre[y])) {
                    pre[y] = x;
                    return true;
                }
            }
        }
        return false;
    }
    
    public int getKM(int judge) {
        if(judge == -1) {  //代表尋找二分圖的最小權匹配
            for(int i = 0;i < n;i++)
                for(int j = 0;j < n;j++)
                    value[i][j] = -1 * value[i][j];  //把權值變為相反數,相當於找最大權匹配
        }
        //初始化lx[i]和ly[i]
        for(int i = 0;i < n;i++) {
            ly[i] = 0;
            lx[i] = Integer.MIN_VALUE;
            for(int j = 0;j < n;j++) {
                if(value[i][j] > lx[i])
                    lx[i] = value[i][j];
            }
        }
    
        for(int i = 0;i < n;i++)
            pre[i] = -1;      //初始化右半部分頂點y的匹配頂點為-1
        
        for(int x = 0;x < n;x++) { //從左半部分頂點開始,尋找二分圖完美匹配的相等子圖完美匹配
            while(true) {
                for(int i = 0;i < n;i++) {//每次尋找x的增廣路徑,初始化sx[i]和sy[i]均為被遍歷
                    sx[i] = false;
                    sy[i] = false;
                }
                if(dfs(x))  //找到從x出發的增廣路徑,結束循環,尋找下一個x的增廣路徑
                    break;
                //下面對於沒有找到頂點x的增廣路徑進行lx[i]和ly[i]值的調整
                int min = Integer.MAX_VALUE;
                for(int i = 0;i < n;i++) {
                    if(sx[i]) {  //當sx[i]已被遍歷時
                        for(int j = 0;j < n;j++) {
                            if(!sy[j]) {  //當sy[j]未被遍歷時
                                if(lx[i] + ly[j] - value[i][j] < min)
                                    min = lx[i] + ly[j] - value[i][j];
                            }
                        }
                    }
                }
                if(min == 0)
                    return -1;
                for(int i = 0;i < n;i++) {
                    if(sx[i])
                        lx[i] = lx[i] - min;
                    if(sy[i])
                        ly[i] = ly[i] + min;
                }
            }
        }
        
        int sum = 0;
        for(int y = 0;y < n;y++) {
            System.out.println("y頂點"+y+"和x頂點"+pre[y]+"匹配");
            if(pre[y] != -1)
                sum = sum + value[pre[y]][y];
        }
        if(judge == -1)
            sum = -1 * sum;
        return sum;
    }
    
    public static void main(String[] args) {
        Main test = new Main();
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        int k = in.nextInt();   //給定二分圖的有向邊數目
        for(int i = 0;i < k;i++) {
            int x = in.nextInt();
            int y = in.nextInt();
            int v = in.nextInt();
            value[x][y] = v;
        }
        System.out.println(test.getKM(1));
    }
}

 

運行結果:

5
10
0 0 2
0 1 3
1 0 2
2 0 4
2 2 2
3 2 1
3 3 3
3 4 2
4 3 8
4 4 3
y頂點0和x頂點2匹配
y頂點1和x頂點0匹配
y頂點2和x頂點1匹配
y頂點3和x頂點4匹配
y頂點4和x頂點3匹配
17

 

 

 

 

參考資料:

   1.求最大權二分匹配的KM算法

 2.二分圖最大權匹配-km算法

 3.二分圖帶權匹配 KM算法與費用流模型建立

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM