二分图最大权匹配-km算法

 

二分图最大权匹配-km算法

分类: C++ 算法   102人阅读  评论(0)  收藏  举报
百度百科是错的,关于相等子图的那一块。

穷举的效率-n!,我们需要更加优秀的算法。
定理:
设M是一个带权完全二分图G的一个完备匹配,给每个顶点一个可行顶标(第i个x顶点的可行标用lx[i]表示,第j个y顶点的可行标用ly[j]表示),如果对所有的边(i,j) in G,都有lx[i]+ly[j]>=w[i,j]成立(w[i,j]表示边的权),且对所有的边(i,j) in M,都有lx[i]+ly[j]=w[i,j]成立,则M是图G的一个最佳匹配。证明很容易。

对于任意的G和M,可行顶标都是存在的:
l(x) = maxw(x,y)
l(y) = 0
欲求完全二分图的最佳匹配,只要用匈牙利算法求其相等子图的完备匹配;问题是当标号之后的Gl无完备匹配时怎么办?1957年(居然比匈牙利算法早???),Kuhn和Munkras给出了一个解决该问题的有效算法,用逐次修改可行顶标l(v)的办法使对应的相等子图之最大匹配逐次增广,最后出现完备匹配。

修改方法如下:
先将一个未被匹配的顶点u(u in {x})做一次增广路,记下哪些结点被访问那些结点没有被访问。求出d=min{lx[i]+ly[j]-w[i,j]}其中i结点被访问,j结点没有被访问。然后调整lx和ly:对于访问过的x顶点,将它的可行标减去d,对于所有访问过的y顶点,将它的可行标增加d。修改后的顶标仍是可行顶标,原来的匹配M仍然存在,相等子图中至少出现了一条不属于M的边,所以造成M的逐渐增广。

上述算法的证明也很容易
Kuhn-Munkras算法流程:
(1)初始化可行顶标的值
(2)用匈牙利算法寻找完备匹配
(3)若未找到完备匹配则修改可行顶标的值
(4)重复(2)(3)直到找到相等子图的完备匹配为止

代码:大家最好是看代码,更能看懂。


[cpp]  view plain copy
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <memory.h>  
  4. #include <algorithm>   
  5.   
  6. using namespace std;  
  7.   
  8. #define MAX 100  
  9.   
  10. int n;  
  11. int weight[MAX][MAX];           //权重  
  12. int lx[MAX],ly[MAX];                //定点标号  
  13. bool sx[MAX],sy[MAX];          //记录寻找增广路时点集x,y里的点是否搜索过  
  14. int match[MAX];                       //match[i]记录y[i]与x[match[i]]相对应  
  15.   
  16. bool search_path(int u) {          //给x[u]找匹配,这个过程和匈牙利匹配是一样的  
  17.     sx[u]=true;  
  18.     for(int v=0; v<n; v++){  
  19.         if(!sy[v] &&lx[u]+ly[v] == weight[u][v]){  
  20.             sy[v]=true;  
  21.             if(match[v]==-1 || search_path(match[v])){  
  22.                 match[v]=u;  
  23.                 return true;  
  24.             }  
  25.         }  
  26.     }  
  27.     return false;  
  28. }  
  29.   
  30. int Kuhn_Munkras(bool max_weight){  
  31.     if(!max_weight){ //如果求最小匹配,则要将边权取反  
  32.         for(int i=0;i<n;i++)  
  33.             for(int j=0;j<n;j++)  
  34.                 weight[i][j]=-weight[i][j];  
  35.     }  
  36.     //初始化顶标,lx[i]设置为max(weight[i][j] | j=0,..,n-1 ), ly[i]=0;  
  37.     for(int i=0;i<n;i++){  
  38.         ly[i]=0;  
  39.         lx[i]=-0x7fffffff;  
  40.         for(int j=0;j<n;j++)  
  41.             if(lx[i]<weight[i][j])  
  42.                 lx[i]=weight[i][j];  
  43.     }  
  44.   
  45.     memset(match,-1,sizeof(match));  
  46.     //不断修改顶标,直到找到完备匹配或完美匹配  
  47.     for(int u=0;u<n;u++){   //为x里的每一个点找匹配  
  48.         while(1){  
  49.             memset(sx,0,sizeof(sx));  
  50.             memset(sy,0,sizeof(sy));  
  51.             if(search_path(u))       //x[u]在相等子图找到了匹配,继续为下一个点找匹配  
  52.                 break;  
  53.             //如果在相等子图里没有找到匹配,就修改顶标,直到找到匹配为止  
  54.             //首先找到修改顶标时的增量inc, min(lx[i] + ly [i] - weight[i][j],inc);,lx[i]为搜索过的点,ly[i]是未搜索过的点,因为现在是要给u找匹配,所以只需要修改找的过程中搜索过的点,增加有可能对u有帮助的边  
  55.             int inc=0x7fffffff;  
  56.             for(int i=0;i<n;i++)  
  57.                 if(sx[i])  
  58.                     for(int j=0;j<n;j++)  
  59.                         if(!sy[j]&&((lx[i] + ly [j] - weight[i][j] )<inc))  
  60.                             inc = lx[i] + ly [j] - weight[i][j] ;  
  61.             //找到增量后修改顶标,因为sx[i]与sy[j]都为真,则必然符合lx[i] + ly [j] =weight[i][j],然后将lx[i]减inc,ly[j]加inc不会改变等式,但是原来lx[i] + ly [j] !=weight[i][j]即sx[i]与sy[j]最多一个为真,lx[i] + ly [j] 就会发生改变,从而符合等式,边也就加入到相等子图中  
  62.             if(inc==0)  cout<<"fuck!"<<endl;  
  63.             for(int i=0;i<n;i++){  
  64.                 if(sx[i])   //  
  65.                     lx[i]-=inc;  
  66.                 if(sy[i])  
  67.                     ly[i]+=inc;  
  68.             }  
  69.         }  
  70.   
  71.     }  
  72.     int sum=0;  
  73.     for(int i=0;i<n;i++)  
  74.         if(match[i]>=0)  
  75.             sum+=weight[match[i]][i];  
  76.   
  77.     if(!max_weight)  
  78.         sum=-sum;  
  79.     return sum;  
  80.   
  81.   
  82. }  
  83. int main(){  
  84.   
  85.     scanf("%d",&n);  
  86.     for(int i=0;i<n;i++)  
  87.         for(int j=0;j<n;j++)  
  88.             scanf("%d",&weight[i][j]);  
  89.     printf("%d\n",Kuhn_Munkras(1));  
  90.     system("pause");  
  91.     return 0;  
  92. }  

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值