二分图匹配--匈牙利算法

二分图匹配--匈牙利算法

基本定义:

二分图 —— 对于无向图G=(V,E),如果存在一个划分使V中的顶点分为两个互不相交的子集,且每个子集中任意两点间不存在边 ϵ∈E,则称图G为一个二分图。

二分图的充要条件是,G至少有两个顶点,且所有回路长度为偶数。

匹配 —— 边的集合,其中任意两条边都不存在公共顶点。
匹配边即是匹配中的元素,匹配点是匹配边的顶点,同样非匹配边,非匹配点相反定义。

最大匹配——在图的所有匹配中,包含最多边的匹配成为最大匹配
完美匹配——如果在一个匹配中所有的点都是匹配点,那么该匹配称为完美匹配。

附注:所有的完美匹配都是最大匹配,最大匹配不一定是完美匹配。假设完美匹配不是最大匹配,那么最大匹配一定存在不属于完美匹配中的边,而图的所有顶点都在完美匹配中,不可能找到更多的边,所以假设不成立,及完美匹配一定是最大匹配。

交替路——从一个未匹配点出发,依次经过非匹配边,匹配边,非匹配边…形成的路径称为交替路,交替路不会形成环。

增广路——起点和终点都是未匹配点的交替路。
因为交替路是非匹配边、匹配边交替出现的,而增广路两端节点都是非匹配点,所以增广路一定有奇数条边。而且增广路中的节点(除去两端节点)都是匹配点,所属的匹配边都在增广路径上,没有其他相连的匹配边,因此如果把增广路径中的匹配边和非匹配边的“身份”交换,就可以获得一个更大的匹配(该过程称为改进匹配)。

示例图

enter description here

Fig1_09_09.JPG
enter description here
enter description here

注释

  • 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下的匈牙利算法:

  1. function Hungarian(c1, m, IsDraw) 
  2. % 匈牙利算法寻找无向二分图的最大匹配 
  3. % inputs: 
  4. % -AdjTable 顶点元素的邻接表,cell结构,每一个元素是一个一维数组, 
  5. % 保存对应节点的邻接节点编号 
  6. % -c1 第一个顶点子集元素个数 
  7. % -m 搜索方法,'B'是广度优先搜索,‘D’是深度优先搜索 
  8. % -IsDraw 是否绘图 
  9.  
  10. if nargin<2 
  11. m='B'
  12. IsDraw=0
  13. elseif nargin<3 
  14. IsDraw=0
  15. end 
  16.  
  17. global MatTable Check AdjTable 
  18. % -MatTable 匹配表,长度为顶点个数,每个元素存放该节点所在匹配边的另一端节点 
  19. % 的编号;如果是非匹配点,则对应值为0 
  20. cn=length(AdjTable);%顶点个数 
  21. MatTable=zeros(1,cn);% 默认都是未匹配点 
  22. Check = zeros(1,cn); % 覆盖过的点不能再访问,否则死循环 
  23.  
  24. EdgesNum=0;% 最大匹配中元素个数 
  25. if m=='D' % 深度优先搜索 
  26. if c1<cn-c1 
  27. for i=1:c1 
  28. if DFS(i
  29. EdgesNum=EdgesNum+1
  30. end 
  31. end 
  32. else 
  33. for j=c1+1:cn 
  34. if DFS(j
  35. EdgesNum=EdgesNum+1
  36. end 
  37. end  
  38. end 
  39. else % 广度优先搜索 
  40. EdgesNum = BFS(c1);  
  41. end 
  42. fprintf('There is %f edges in the biggest matches. ',EdgesNum); 
  43. if IsDraw 
  44. c2=cn-c1; 
  45. X1=ones(c1,1)*40
  46. Y1=20:10:c1*10+19
  47. X2=ones(c2,1)*100
  48. Y2=20:10:c2*10+19
  49. X=[X1;X2]
  50. Y=[Y1';Y2']
  51. color={'ro:','ko:'}
  52. for i=1:cn 
  53. if MatTable(i)~=0 
  54. plot(X(i),Y(i),color{1},'MarkerSize',15);hold on  
  55. if i<=c1 
  56. text(X(i)-3,Y(i),num2str(i)); 
  57. else 
  58. text(X(i)+3,Y(i),num2str(i)); 
  59. end 
  60. else 
  61. plot(X(i),Y(i),color{2},'MarkerSize',15);hold on 
  62. if i<c1 
  63. text(X(i)-3,Y(i),num2str(i)); 
  64. else 
  65. text(X(i)+3,Y(i),num2str(i)); 
  66. end 
  67. end 
  68. end 
  69. for i=1:cn 
  70. for j=1:length(AdjTable{i}
  71. if MatTable(i)==AdjTable{i}(j
  72. plot(X([i,AdjTable{i}(j)]),Y([i,AdjTable{i}(j)]),'r.-','LineWidth',2);hold on 
  73. else 
  74. plot(X([i,AdjTable{i}(j)]),Y([i,AdjTable{i}(j)]),'k.-','LineWidth',2);hold on 
  75. end 
  76. end 
  77. end 
  78. box('on'
  79. axis off 
  80. hold off 
  81.  
  82. end 
  83.  
  84.  
  85. end 
  86. % AdjTable 邻接表 
  87. % MatTable 匹配表 
  88. function bool=DFS(u) 
  89. % n 是左侧未匹配点的编号 
  90. % 寻找节点n的一条未匹配边 
  91. global AdjTable MatTable Check 
  92. for i=1:length(AdjTable{u}
  93. v=AdjTable{u}(i); 
  94. if ~Check(v) 
  95. Check(v)=1
  96. if MatTable(v) == 0|| DFS(MatTable(v))  
  97. %v是未匹配点则找到增广路径,交换身份 
  98. %否则,如果v的匹配点存在增广路径, 
  99. %那么也是找到一条增广路径 
  100. % || 是短路运算符 
  101. MatTable(u) = v; 
  102. MatTable(v) = u; 
  103. bool=1
  104. return;  
  105. end  
  106. end 
  107. end 
  108. bool=0
  109. end 
  110.  
  111. function EdgeNum=BFS(c1) 
  112. global AdjTable MatTable Check 
  113. pre = zeros(length(AdjTable))-1
  114. % 存放的是该点所在的非匹配边的前一个非匹配边的右端端点编号 
  115. queue=[];% 广度优先搜索需要的搜索队列 
  116. EdgeNum=0;%最大匹配元素个数 
  117. for i=1:c1 
  118. if ~MatTable(i) % 寻找未匹配点 
  119. queue=[i];%入队列 
  120. flag = 0; % 未找到增广路径 
  121. pre(i)=-1; % 为了最后改进路径时设定终点  
  122. while(~isempty(queue)&&~flag) 
  123. u=queue(1); 
  124. queue(1)=[];%出队列 
  125. edges=AdjTable{u}
  126. for j=1:length(edges) 
  127. v = edges(j); 
  128. if ~flag && Check(v)~=i 
  129. Check(v)=i
  130. queue=[queue,MatTable(v)]
  131. %找到一条匹配路,将匹配路的右端节点放入队列 
  132. if MatTable(v) % 非增广路 
  133. pre(MatTable(v))=u; 
  134. %下一条非匹配边的起点对应前一条非匹配边的起点 
  135. else % 找到增广路径 
  136. flag=1
  137. d=u; 
  138. e=v; 
  139. while d~=-1 
  140. t = MatTable(d); 
  141. MatTable(d)=e; 
  142. MatTable(e)=d; 
  143. d = pre(d); 
  144. e = t; 
  145. end 
  146. end 
  147. end 
  148. end 
  149. end 
  150. end 
  151. if MatTable(i)~=0 %表示找到增广路径了,此时起点肯定在匹配边上 
  152. EdgeNum=EdgeNum+1
  153. end 
  154. end 
  155. end 
  156.  
  157.  
  158. function testHungarian 
  159. c1=5
  160. c2=5
  161. % AdjMatrix=randi(2,c1,c2)-1; 
  162. t=0.7
  163. AdjMatrix=rand(c1,c2)>t; 
  164. % AdjMatrix=ones(c1,c2); 
  165. global AdjTable 
  166. AdjTable = cell(c1+c2,1); 
  167. for i=1:c1 
  168. t=find(AdjMatrix(i,:)~=0); 
  169. AdjTable{i}=c1+t; 
  170. end 
  171. for j=1:c2 
  172. t=find(AdjMatrix(:,j)~=0); 
  173. AdjTable{c1+j}=t; 
  174. end 
  175.  
  176. Hungarian(c1,'B',1); 
  177. end 
  178.  

分析
参考的blog中指出算法的时间复杂度为,实际应用中使用BFS的算法比DFS算法更快,但是在matlab代码中,发现使用DFS算法的搜索比BFS算法搜索的速度快不少,尤其是顶点和边数比较大的情形。


参考文献
Renfei Song's Blog
百度百科-二分图

原文地址:https://www.cnblogs.com/YiXiaoZhou/p/5875040.html