首页 > 代码库 > Bipartitegraph2195EM

Bipartitegraph2195EM

EM算法:

 

 

 

 

该算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。设顶点 Xi 的顶标为 A[ i ],顶点 Yj 的顶标为 B[ j ],顶点 Xi 与 Yj 之间的边权为 w[i,j]

在算法执行过程中的任一时刻,对于任一条边(i,j)A[ i ]+B[j]>=w[i,j]始终成立。

初始时为了使 A[ i ]+B[j]>=w[i,j]恒成立,令 A[ i ]为所有与顶点 Xi 关联的边的最大权,B[j]=0

KM 算法的正确性基于以下定理:

若由二分图中所有满足 A[ i ]+B[j]=w[i,j]的边(i,j)1.所有边满足 A[ i ]+B[j]=w[i,j])构成的子图(称做相等子图)有完备匹配(2.是完备匹配),那么这个完备匹配就是二分图的最大权匹配。

 

首先解释下什么是完备匹配,所谓的完备匹配就是在二部图中,点集中的所有点(X顶点比Y顶点少的时候)都有对应的匹配或者是点集中所有的点(Y顶点比X顶点少的时候)都有对应的匹配,则称该匹配为完备匹配。

 

这个定理是显然的。因为对于二分图的任意一个匹配,如果它属于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不属于相等子图,那么它的边权和小于所有顶点的顶标和(说明边选取的不是最优的)。所以相等子图的完备匹配一定是二分图的最大权匹配。

 

我们求当前相等子图的完备匹配失败了,是因为对于某个 顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,它的叶子结点全部是 顶点。现在我们把交错树中 顶点的顶标全都减小某个值 d交错书顶点(只对树里面的进行修改)的顶标全都增加同一个值 d,那么我们会发现:

1)两端都在交错树中的边(i,j)A[ i ]+B[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。

2)两端都不在交错树中的边(i,j)A[ i ]和 B[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。

3端不在交错树中,端在交错树中的边(i,j),它的 A[ i ]+B[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。

4端在交错树中,端不在交错树中的边(i,j),它的 A[ i ]+B[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。(针对之后例子中 x1->y4 这条边)

 

现在的问题就是求 值了。为了使 A[ i ]+B[j]>=w[i,j]始终成立,且至少有一条边进入相等子图,应该等于:

Min{A[i]+B[j]-w[i,j] | Xi 在交错树中,Yi 不在交错树中}

A’[i]=A[i]-(A[i]]+B[j]-w[i,j])=w[i]-B[j]

B’[j]=B[j]

A’[i]+B’[j]=w[i,j]

另外d为最小的,因此该边是能取到的最大的一个(x需要减D),保证了最优化。

 

从上面四条可以看到前三条肯定不会出现错误,唯有可能产生错误的是第四条,因为可能 A[ i ]+B[j]减小后,比w[ij]还小,那么就错了。所以,本来应该从所有的边中寻找最小Min{A[i]+B[j]-w[i,j] ,但因为可能出现错误的,只会是第四条,因此从Xi 在交错树中,Yi 不在交错树中寻找。

 

 

 

 

[KM算法的几种转化]

 

KM算法是求最大权完备匹配,如果要求最小权完备匹配怎么办?方法很简单,只需将所有的边权值取其相反数,求最大权完备匹配,匹配的值再取相反数即可。

 

KM算法的运行要求是必须存在一个完备匹配,如果求一个最大权匹配(不一定完备)该如何办?依然很简单,把不存在的边权值赋为0

 

KM算法求得的最大权匹配是边权值和最大,如果我想要边权之积最大,又怎样转化?还是不难办到,每条边权取自然对数,然后求最大和权匹配,求得的结果a再算出e^a就是最大积匹配。至于精度问题则没有更好的办法了。

给了两个算法,其实后面优化的了 更容易理解

 

 

#include<iostream>

#include<vector>

#include<cmath>

using namespace std;

const int MAXN = 110;

const int inf = -0x3ffffff;

 

int count_h;

struct node

{

int pos_x,pos_y;

int node_num;

node()

{

pos_x=0,pos_y=0;

    node_num=0;

}

};

int bin_map[MAXN][MAXN];

char ch[MAXN][MAXN];

bool vis_x[MAXN],vis_y[MAXN];

int lx[MAXN],ly[MAXN];

int result[MAXN];

bool find(int a,int n)//判断第a能否找到房子

{

vis_x[a] = true;//直接假设首次访问的节点已经加入相等子图

for(int i=1;i!=n;i++)

{

if(!vis_y[i]&&bin_map[a][i]==lx[a]+ly[i])//bin_map[a][i]==lx[a]+ly[i]即为相等子图的条件

{

vis_y[i] = true;//y集合中满足相等子图的i都被标记

if(result[i]==-1||find(result[i],n))

{

result[i] = a;

return true;

}

}

}

return false;

}

int best_match(int n)//开始匹配

{

memset(ly,0,sizeof(ly));//y标值为0

for(int i=1;i!=n;i++)

{

lx[i] = inf;

for(int j=1;j!=n;j++)//这里的题目 人和房子数目是一样的

{

if(lx[i]<bin_map[i][j])

lx[i] = bin_map[i][j];//X的标值为最大的那个

}

}

memset(result,-1,sizeof(result));

for(int u=1;u!=n;u++)

{

while(1)

{

memset(vis_x,false,sizeof(vis_x));//将前面的x集合中相等子图的节点标记为不在里面。

memset(vis_y,false,sizeof(vis_y));

if(find(u,n))//找到了,才继续下一个u。如果找不到更新lx[],ly[]后仍然在while(1)中没有出来,FIND函数之后,标记的VIST[X]是指本来就在相等子图里面的,标记的VISIT[Y]是指原先在相等子图的。但是,可能存在部分本来在相等子图 却没有标记的情况(标记必定是成对出现,即一个X里面的一个Y里面的),但是没关系,因为他们在后面因为VISIT=0,所以不改变值

break;

int dx = -inf;

for(int i=1;i!=n;i++)

{

if(vis_x[i])//X中在相等子图的 {

for(int j=1;j!=n;j++)

{

if(!vis_y[j])//y不在相等子图中 dx = min(dx,lx[i]+ly[j]-bin_map[i][j]);

}

}

}

for(int i=1;i!=n+1;i++)

{//修改时对所有VISIT过的节点修改,原因就是上面提到的四条

if(vis_x[i])

lx[i]-=dx;

if(vis_y[i])

ly[i]+=dx;

}

}

}

int sum = 0;

for(int i=1;i!=n;i++)

{

sum-=bin_map[result[i]][i];//result标记最后想要的值

}

return sum;

}

int main()

{

int m,n;

while(cin>>m>>n&&(m!=0&&n!=0))

{

vector<node> men;

vector<node> house;

int count_m(0);

count_h=0;

for(int i=1;i!=m+1;i++)

{

for(int j=1;j!=n+1;j++)

{

cin>>ch[i][j];

if(ch[i][j]==‘m‘)

{

count_m++;

node N;N.pos_x = j;N.pos_y = i;N.node_num = count_m;

men.push_back(N);

}

if(ch[i][j]==‘H‘)

{

count_h++;

node N;N.pos_x = j;N.pos_y = i;N.node_num = count_h;

house.push_back(N);

}

}

}

memset(bin_map,0,sizeof(bin_map));

for(vector<node> ::size_type t=0;t!=house.size();t++)//构造二分图

{

node N1 = men[t]; 

for(vector<node> ::size_type i=0;i!=house.size();i++)

{

int x2 = house[i].pos_x;int y2 = house[i].pos_y;

int x1 = N1.pos_x;int y1 = N1.pos_y;

int num = -(abs(x1-x2)+abs(y1-y2));//值变为负数转化为,求最大匹配

//KM算法是求最大权完备匹配,如果要求最小权完备匹配怎么办?方法很简单,只需将所有的边权值取其相反数,求最大权完备匹配,匹配的值再取相反数即可

bin_map[N1.node_num][house[i].node_num] = num; 

}

}

int cost = best_match(count_m+1);

cout<<cost<<endl;

}

return 0;

}

 

以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶标,每次修改顶标时由于要枚举边来求d值,

复杂度为O(n2)

实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数 slack,每次开始找增广路时初始化为无穷大。在寻找增广路的过程中,

检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与A [i]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为

d值即可,即在DFS阶段就得到该减的东西,而不是重新回头找)

 

 

 

#include   <iostream>

using namespace std;

const int maxn = 105;

const int inf = 1000000000;

typedef struct   Man

{

int x,y;

}Man;

int map[maxn][maxn];

bool x[maxn],y[maxn];

int my[maxn];

int lx[maxn],ly[maxn];

int slack[maxn];

bool   dfs(int t,int N)

{

   int u;

   int wt;

      x[t] = 1;

for(u=1;u<=N;u++)

   wt = map[t][u]-lx[t]-ly[u];

if(!y[u]&&!wt)

   {

      y[u] = 1;

      if(my[u]==-1||dfs(my[u],N))

      {

        my[u] = t;

        return   true;                           

      }                

   }

   else if(y[u]==0 && slack[u]>wt) slack[u] = wt;//不满足。如果y[u]=0, map[t][u]!=lx[t]+ly[u],恰恰是我门需要修改的增广路,对于y[u]=1的情况,没有必要存储

}    

     return    false; 

}

 

int perfect_match(int N)

{

   int i,j,k;

   for(i=1;i<=N;i++)

   {

   my[i] = -1;

   ly[i] = 0;

   lx[i] = inf;//求最小权这样,最大权应该是-inf; 

   for(j=1;j<=N;j++)

   if(map[i][j]<lx[i])lx[i] = map[i][j];//求最小权,因此起始状态是lx[]+ly[]<=map[i][j],lx[i]i点上边最小的那个,ly0.只有正确匹配,map[i][j]//才能取得最小值

   }   

   for(k=1;k<=N;k++)

   {

     memset(x,0,sizeof(x));

     memset(y,0,sizeof(y));

     for(i=1;i<=N;i++) slack[i] = inf;

     while(!dfs(k,N))

     {

      int d = inf;

      for(i=1;i<=N;i++)

      if(slack[i]<d) d = slack[i];//slack里面存储的全部是Y[I]=0

      for(i=1;i<=N;i++)

       {

         if(x[i]) {lx[i]+=d; x[i] = 0;}//最小权和,之前 lx[]+ly[]<map[i][j],为使得等式成立,LX变大

         if(y[i]) {ly[i]-=d; y[i] = 0;}// 

       }       

     }                

   }     

   int ans = 0;

   for(i=1;i<=N;i++)

     ans+=map[my[i]][i];

     return ans; 

}

int main()

{

int i,j,k,l;

int n,m;

char an[maxn][maxn];

Man M[maxn],Hou[maxn];

while(scanf("%d%d",&n,&m)!=EOF)

{

   if(n==0&&m==0) break;

   k = l = 0;

   for(i=0;i<n;i++)

   { scanf("%s",an[i]);

       for(j=0;j<m;j++)

        {

           if(an[i][j]==‘m‘){M[++k].x = i; M[k].y = j;}

            if(an[i][j]==‘H‘){Hou[++l].x = i; Hou[l].y = j;} 

          } 

    }

for(i=1;i<=k;i++)

   for(j=1;j<=l;j++)

   {

      map[i][j] = (abs(M[i].x-Hou[j].x)+abs(M[i].y-Hou[j].y));                

    }

printf("%d\n",perfect_match(k));

   

     

}

Bipartitegraph2195EM