如圖,所示,最大匹配爲4:.
如圖所示,對於一個二分圖,令已有的邊的容量(Capacity)爲無窮大,增加一個源點s和一個匯點t,令s和t分別連接二部圖中的一個分步,並設置其容量爲1。這時得到流網絡G’,計算得到的最大流就等於最大二分匹配。
但是,我們還必須回答爲什麼這種方法是可行的呢?這裏有一個簡單的證明方法。首先假設,當前流網絡有一個最大流,但它對應的不是最大匹配,那麼,我們至少還可以向最大匹配中加入一條邊,設爲(u,v),顯然我們還可以增加條增廣路徑,s->u->v->t。那麼就得到一個更大的流,和假設矛盾。所以假設不成立。同理,假設當前有一個最大匹配,其對應的不是最大流,那麼至少還存在一條增廣路徑,假設s->u->v->t。這時就可以增加邊(u,v)到最大匹配中,矛盾。
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;
}
}