圖的匹配問題與最大流問題(五)——計算二分圖的最大匹配

二分圖的最大匹配問題第一篇已經說過,下面看看百度百科給的一些解釋:

給定一個二分圖G,M爲G邊集的一個子集,如果M滿足當中的任意兩條邊都不依附於同一個頂點,則稱M是一個匹配
極大匹配(Maximal Matching)是指在當前已完成的匹配下,無法再通過增加未完成匹配的邊的方式來增加匹配的邊數。最大匹配(maximum matching)是所有極大匹配當中邊數最大的一個匹配。選擇這樣的邊數最大的子集稱爲圖的最大匹配問題。
如果一個匹配中,圖中的每個頂點都和圖中某條邊相關聯,則稱此匹配爲完全匹配,也稱作完備匹配。
計算二分圖最大匹配可以用最大流(Maximal Flow)或者匈牙利算法(Hungarian Algorithm)。

如圖,所示,最大匹配爲4:.

 


一、最大流方法計算最大匹配
最大流問題已經前面已經解釋了很多了,不妨可以回去熟悉下先。最大流問題Ford-Fulkerson解法

如圖所示,對於一個二分圖,令已有的邊的容量(Capacity)爲無窮大,增加一個源點s和一個匯點t,令s和t分別連接二部圖中的一個分步,並設置其容量爲1。這時得到流網絡G’,計算得到的最大流就等於最大二分匹配。

但是,我們還必須回答爲什麼這種方法是可行的呢?這裏有一個簡單的證明方法。首先假設,當前流網絡有一個最大流,但它對應的不是最大匹配,那麼,我們至少還可以向最大匹配中加入一條邊,設爲(u,v),顯然我們還可以增加條增廣路徑,s->u->v->t。那麼就得到一個更大的流,和假設矛盾。所以假設不成立。同理,假設當前有一個最大匹配,其對應的不是最大流,那麼至少還存在一條增廣路徑,假設s->u->v->t。這時就可以增加邊(u,v)到最大匹配中,矛盾。
代碼就很簡單了,構造流網絡G‘,然後調用最大流算法即得到結果。因爲二分圖上任何匹配的勢之多爲min(L,R)(L代表二分圖的左部點集,R代表二分圖的右部點集),所以G’中最大流的值爲O(V),因此,可以再O(VE‘)=O(VE)的時間內,找出二分圖的最大匹配。
二、匈牙利算法
這可是一個大名鼎鼎的算法,它實際上是對最大流算法的一種改進,提高了效率。網上有一篇文章將的很詳細,很受用,可以參考:http://imlazy.ycool.com/post.1603708.html。我就不贅述了,直接上代碼了,Java實現。
package matchings;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import entry.Edge;
import entry.Node;

import maxflow.FordFulkerson;

public class Matching {

	private double graph[][];
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		double graph[][]={
		{0,0,0,1,1,0},
		{0,0,0,0,1,1},
		{0,0,0,0,0,1},
		{0,0,0,0,0,0},
		{0,0,0,0,0,0},
		{0,0,0,0,0,0}
		};
		double graph2[][]={
				{0,0,0,0,0,1,0,0,0},
				{0,0,0,0,0,1,0,1,0},
				{0,0,0,0,0,0,1,1,1},
				{0,0,0,0,0,0,0,1,0},
				{0,0,0,0,0,0,0,1,0},
				{0,0,0,0,0,0,0,0,0},
				{0,0,0,0,0,0,0,0,0},
				{0,0,0,0,0,0,0,0,0},
				{0,0,0,0,0,0,0,0,0}
				};
		Matching m=new Matching(graph2);
		int L[]={0,1,2,3,4};
		int R[]={5,6,7,8};
		/*
		double flowNetwork[][]=m.getMaxMachingFromMaxFlow(graph2,L,R);
		
		int length=flowNetwork.length;
		for(int i=0;i<length;i++)
		{
			for(int j=0;j<length;j++)
			{
				
				System.out.print((flowNetwork[i][j]>0?flowNetwork[i][j]:0.0)+" ");
			}
			System.out.println();
		}*/
		System.out.println(m.getMaxMaching(graph2, L, R));
	}
	public Matching(double graph[][])
	{
		this.graph=graph;
	}
	/**
	 * Let G be a bipartite graph with partite sets U and W such that r=|U|<=|W|.
	 * Then G contains a matching of cardinality r if and only if U is neighborly.(which means that :For any nonempty subset X,N(X)>=X)
	 * 但是這種方法的複雜度爲O(2^n),所以可以不如用下面兩個算法直接求出最大匹配,然後比對是否是perfect matching
	 * @return
	 */
	public boolean hasPerfectMatching(double graph[][],int L[],int R[])
	{
		
		return false;
	}
	
	/**
	 * 利用最大流算法實現,返回最大流網絡
	 * @param graph
	 * @param L
	 * @param R
	 * @return
	 */
	public double[][] getMaxMachingFromMaxFlow(double graph[][],int L[],int R[])
	{
		int length=graph.length;
		double augGraph[][]=new double[length+2][length+2];
		for(int i=0;i<L.length;i++)
		{
			augGraph[0][L[i]+1]=1.0;
		}
		for(int i=0;i<R.length;i++)
		{
			augGraph[R[i]+1][length+1]=1.0;
		}
		for(int i=1;i<length+1;i++)
		{
			for(int j=1;j<length+1;j++)
			{
				augGraph[i][j]=graph[i-1][j-1];
			}
		}
		/*for(int i=0;i<length+2;i++)
		{
			for(int j=0;j<length+2;j++)
			{
				
				System.out.print(augGraph[i][j]+" ");
			}
			System.out.println();
		}*/
		double maxF=FordFulkerson.edmondsKarpMaxFlow(augGraph, 0, length+1);
		double flowNetwork[][]=FordFulkerson.getFlowNetwork();
		
		return flowNetwork;
	}
	
	/**
	 * 匈牙利算法http://imlazy.ycool.com/post.1603708.html
	 * (1)有奇數條邊。
	 * (2)起點在二分圖的左半邊,終點在右半邊。
	   (3)路徑上的點一定是一個在左半邊,一個在右半邊,交替出現。(其實二分圖的性質就決定了這一點,因爲二分圖同一邊的點之間沒有邊相連,不要忘記哦。)
	   (4)整條路徑上沒有重複的點。
	   (5)起點和終點都是目前還沒有配對的點,而其它所有點都是已經配好對的。(如圖1、圖2所示,[1,5]和[2,6]在圖1中是兩對已經配好對的點;而起點3和終點4目前還沒有與其它點配對。)
	   (6)路徑上的所有第奇數條邊都不在原匹配中,所有第偶數條邊都出現在原匹配中。(如圖1、圖2所示,原有的匹配是[1,5]和[2,6],這兩條配匹的邊在圖2給出的增廣路徑中分邊是第2和第4條邊。而增廣路徑的第1、3、5條邊都沒有出現在圖1給出的匹配中。)
	   (7)最後,也是最重要的一條,把增廣路徑上的所有第奇數條邊加入到原匹配中去,並把增廣路徑中的所有第偶數條邊從原匹配中刪除(這個操作稱爲增廣路徑的取反),則新的匹配數就比原匹配數增加了1個。(如圖2所示,新的匹配就是所有藍色的邊,而所有紅色的邊則從原匹配中刪除。則新的匹配數爲3。)
	   
	 * @param graph
	 * @param L
	 * @param R
	 */
	public List<Edge> getMaxMaching(double graph[][],int L[],int R[])
	{
		int length=graph.length;
		for(int i=0;i<length;i++)
		{
			for(int j=i;j<length;j++)
			{
				graph[j][i]=graph[i][j];
			}
		}
		Set<Integer> rSet=new HashSet<Integer>();
		Set<Integer> lSet=new HashSet<Integer>();
		List<Edge> list=new ArrayList<Edge>();
		for(int i=0;i<L.length;i++)
		{
			if(lSet.contains(L[i]))
				continue;
			Node result=null;
			int j=0;
			while(j<R.length)
			{
				result=FordFulkerson.augmentPath(graph, L[i], R[j]);
				if(result==null||rSet.contains(R[j]))
					j++;
				else
				{
					boolean b=reverse(result,list,rSet,lSet);
					//反正成功,直接跳出循環,從下一個左部點繼續需找增廣路徑,否則在繼續在該點尋找增廣路徑,知道找不到爲止
					if(b)
						break;
					else
						j++;
				}
			}
					
		}
		
		return list;
	}
	/**
	 * 如果證明該增廣路徑滿足上述6個性質,則進行反轉操作,並返回true,否則什麼也不做,返回false
	 * @param result
	 * @param list
	 * @param rSet
	 * @param lSet
	 * @return
	 */
	private boolean reverse(Node result,List<Edge> list,Set<Integer> rSet,Set<Integer> lSet) {

		int idx=0;
		List<Edge> oddEdge=new ArrayList<Edge>();
		List<Edge> evenEdge=new ArrayList<Edge>();
		while(result.getParent()!=null)
		{
			Node parent=result.getParent();
			
			if(idx%2==0)
			{
				Edge e=new Edge(parent.nodeId,result.nodeId,0);
				evenEdge.add(e);
				
			}
			else
			{
				Edge e=new Edge(result.nodeId,parent.nodeId,0);
				oddEdge.add(e);
			}
			idx++;
			result=parent;
		}
		/**
		 * 檢測第6條性質
		 */
		for(int i=0;i<oddEdge.size();i++)
		{
			if(!list.contains(oddEdge.get(i)))
				return false;
			else
			{
				list.remove(oddEdge.get(i));
				//System.out.println("remove: "+oddEdge.get(i));
			}
		}
		for(int i=0;i<evenEdge.size();i++)
		{
			list.add(evenEdge.get(i));
			lSet.add(evenEdge.get(i).u);
			rSet.add(evenEdge.get(i).v);
			//System.out.println("add: "+evenEdge.get(i));
		}
		return true;
	}

}






發佈了23 篇原創文章 · 獲贊 29 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章