二分圖匹配--匈牙利算法
基本定義:
二分圖 —— 對於無向圖G=(V,E),如果存在一個划分使V中的頂點分為兩個互不相交的子集,且每個子集中任意兩點間不存在邊 ϵ∈E,則稱圖G為一個二分圖。
二分圖的充要條件是,G至少有兩個頂點,且所有回路長度為偶數。
匹配 —— 邊的集合,其中任意兩條邊都不存在公共頂點。
匹配邊即是匹配中的元素,匹配點是匹配邊的頂點,同樣非匹配邊,非匹配點相反定義。
最大匹配——在圖的所有匹配中,包含最多邊的匹配成為最大匹配
完美匹配——如果在一個匹配中所有的點都是匹配點,那么該匹配稱為完美匹配。
附注:所有的完美匹配都是最大匹配,最大匹配不一定是完美匹配。假設完美匹配不是最大匹配,那么最大匹配一定存在不屬於完美匹配中的邊,而圖的所有頂點都在完美匹配中,不可能找到更多的邊,所以假設不成立,及完美匹配一定是最大匹配。
交替路——從一個未匹配點出發,依次經過非匹配邊,匹配邊,非匹配邊…形成的路徑稱為交替路,交替路不會形成環。
增廣路——起點和終點都是未匹配點的交替路。
因為交替路是非匹配邊、匹配邊交替出現的,而增廣路兩端節點都是非匹配點,所以增廣路一定有奇數條邊。而且增廣路中的節點(除去兩端節點)都是匹配點,所屬的匹配邊都在增廣路徑上,沒有其他相連的匹配邊,因此如果把增廣路徑中的匹配邊和非匹配邊的“身份”交換,就可以獲得一個更大的匹配(該過程稱為改進匹配)。
示例圖



注釋:
Fig3是一個二分圖G=(V,E),V={1,2,3,4,5,6,7,8},E={(1,7),(1,5),(2,6),(3,5),(3,8),(4,5),(4,6)},該圖可以重繪成Fig4,V可分成兩個子集V={V1,V2},V1={1,2,3,4},V2={5,6,7,8}。
Fig4中的紅色邊集合就是一個匹配{(1,5),(4,6),(3,8)}
Fig2中是最大匹配
Fig1中紅色邊集合是完美匹配
Fig1中交替路舉例(4-6-2-7-1-5)
Fig4中增廣路(2-6-4-5-1-7)
匈牙利樹
匈牙利樹中從根節點到葉節點的路徑均是交替路,且匈牙利樹的葉節點都是匹配點。
匈牙利算法
求解最大匹配的算法,通過不斷的尋找增廣路徑,並將增廣路徑進行改進匹配,直至找不到更多的增廣路徑。
二分圖的最大匹配可以通過匈牙利樹的搜索尋找增廣路徑來獲得,而樹的搜索可以使用深度優先搜索(DFS)或者廣度優先搜索(BFS)
下面使用matlab代碼實現DFS和BFS下的匈牙利算法:
- function Hungarian(c1, m, IsDraw)
- % 匈牙利算法尋找無向二分圖的最大匹配
- % inputs:
- % -AdjTable 頂點元素的鄰接表,cell結構,每一個元素是一個一維數組,
- % 保存對應節點的鄰接節點編號
- % -c1 第一個頂點子集元素個數
- % -m 搜索方法,'B'是廣度優先搜索,‘D’是深度優先搜索
- % -IsDraw 是否繪圖
-
- if nargin<2
- m='B';
- IsDraw=0;
- elseif nargin<3
- IsDraw=0;
- end
-
- global MatTable Check AdjTable
- % -MatTable 匹配表,長度為頂點個數,每個元素存放該節點所在匹配邊的另一端節點
- % 的編號;如果是非匹配點,則對應值為0
- cn=length(AdjTable);%頂點個數
- MatTable=zeros(1,cn);% 默認都是未匹配點
- Check = zeros(1,cn); % 覆蓋過的點不能再訪問,否則死循環
-
- EdgesNum=0;% 最大匹配中元素個數
- if m=='D' % 深度優先搜索
- if c1<cn-c1
- for i=1:c1
- if DFS(i)
- EdgesNum=EdgesNum+1;
- end
- end
- else
- for j=c1+1:cn
- if DFS(j)
- EdgesNum=EdgesNum+1;
- end
- end
- end
- else % 廣度優先搜索
- EdgesNum = BFS(c1);
- end
- fprintf('There is %f edges in the biggest matches.\n',EdgesNum);
- if IsDraw
- c2=cn-c1;
- X1=ones(c1,1)*40;
- Y1=20:10:c1*10+19;
- X2=ones(c2,1)*100;
- Y2=20:10:c2*10+19;
- X=[X1;X2];
- Y=[Y1';Y2'];
- color={'ro:','ko:'};
- for i=1:cn
- if MatTable(i)~=0
- plot(X(i),Y(i),color{1},'MarkerSize',15);hold on
- if i<=c1
- text(X(i)-3,Y(i),num2str(i));
- else
- text(X(i)+3,Y(i),num2str(i));
- end
- else
- plot(X(i),Y(i),color{2},'MarkerSize',15);hold on
- if i<c1
- text(X(i)-3,Y(i),num2str(i));
- else
- text(X(i)+3,Y(i),num2str(i));
- end
- end
- end
- for i=1:cn
- for j=1:length(AdjTable{i})
- if MatTable(i)==AdjTable{i}(j)
- plot(X([i,AdjTable{i}(j)]),Y([i,AdjTable{i}(j)]),'r.-','LineWidth',2);hold on
- else
- plot(X([i,AdjTable{i}(j)]),Y([i,AdjTable{i}(j)]),'k.-','LineWidth',2);hold on
- end
- end
- end
- box('on')
- axis off
- hold off
-
- end
-
-
- end
- % AdjTable 鄰接表
- % MatTable 匹配表
- function bool=DFS(u)
- % n 是左側未匹配點的編號
- % 尋找節點n的一條未匹配邊
- global AdjTable MatTable Check
- for i=1:length(AdjTable{u})
- v=AdjTable{u}(i);
- if ~Check(v)
- Check(v)=1;
- if MatTable(v) == 0|| DFS(MatTable(v))
- %v是未匹配點則找到增廣路徑,交換身份
- %否則,如果v的匹配點存在增廣路徑,
- %那么也是找到一條增廣路徑
- % || 是短路運算符
- MatTable(u) = v;
- MatTable(v) = u;
- bool=1;
- return;
- end
- end
- end
- bool=0;
- end
-
- function EdgeNum=BFS(c1)
- global AdjTable MatTable Check
- pre = zeros(length(AdjTable))-1;
- % 存放的是該點所在的非匹配邊的前一個非匹配邊的右端端點編號
- queue=[];% 廣度優先搜索需要的搜索隊列
- EdgeNum=0;%最大匹配元素個數
- for i=1:c1
- if ~MatTable(i) % 尋找未匹配點
- queue=[i];%入隊列
- flag = 0; % 未找到增廣路徑
- pre(i)=-1; % 為了最后改進路徑時設定終點
- while(~isempty(queue)&&~flag)
- u=queue(1);
- queue(1)=[];%出隊列
- edges=AdjTable{u};
- for j=1:length(edges)
- v = edges(j);
- if ~flag && Check(v)~=i
- Check(v)=i;
- queue=[queue,MatTable(v)];
- %找到一條匹配路,將匹配路的右端節點放入隊列
- if MatTable(v) % 非增廣路
- pre(MatTable(v))=u;
- %下一條非匹配邊的起點對應前一條非匹配邊的起點
- else % 找到增廣路徑
- flag=1;
- d=u;
- e=v;
- while d~=-1
- t = MatTable(d);
- MatTable(d)=e;
- MatTable(e)=d;
- d = pre(d);
- e = t;
- end
- end
- end
- end
- end
- end
- if MatTable(i)~=0 %表示找到增廣路徑了,此時起點肯定在匹配邊上
- EdgeNum=EdgeNum+1;
- end
- end
- end
-
-
- function testHungarian
- c1=5;
- c2=5;
- % AdjMatrix=randi(2,c1,c2)-1;
- t=0.7;
- AdjMatrix=rand(c1,c2)>t;
- % AdjMatrix=ones(c1,c2);
- global AdjTable
- AdjTable = cell(c1+c2,1);
- for i=1:c1
- t=find(AdjMatrix(i,:)~=0);
- AdjTable{i}=c1+t;
- end
- for j=1:c2
- t=find(AdjMatrix(:,j)~=0);
- AdjTable{c1+j}=t;
- end
-
- Hungarian(c1,'B',1);
- end
-
分析:
參考的blog中指出算法的時間復雜度為,實際應用中使用BFS的算法比DFS算法更快,但是在matlab代碼中,發現使用DFS算法的搜索比BFS算法搜索的速度快不少,尤其是頂點和邊數比較大的情形。