我们已经学习了KM算法,现在我们来分析一下它的时间复杂度。
bool dfs(int s)
{
visx[s]=1;
for(int i=1;i<=cnty;i++)
if(!visy[i])
{
int t=wx[s]+wy[i]-dis[s][i];
if(t==0)
{
visy[i]=1;
if(linky[i]==0||dfs(linky[i]))
{
linkx[s]=i,linky[i]=s;
return true;
}
}
else if(t>0)
if(treturn false;
}
void km()
{
memset(linkx,0,sizeof linkx);
memset(linky,0,sizeof linky);
for(int i=1;i<=cntx;i++)
{
while(1)
{
minz=INF;
memset(visx,0,sizeof visx);
memset(visy,0,sizeof visy);
if(dfs(i))break;
for(int j=1;j<=cntx;j++) //将交错树中X部的点的顶标减去minz
if(visx[j])wx[j]-=minz;
for(int j=1;j<=cnty;j++) //将交错树中Y部的点的顶标加上minz
if(visy[j])wy[j]+=minz;
}
}
}
KM算法每修改一次顶标需要调用一次dfs找增广路径,如果采用邻接矩阵存储边权,则一次dfs的时间复杂度是O(N^2)。那么需要修改多少次顶标呢?我们知道修改一次顶标必然导致至少一条边加入相等子图。最多有n^2条边,所以最多会修改顶标n^2次。所以总的时间复杂度是O(N^4)。
网上有人说,加一个slack数组记录最小差,可以降低时间复杂度—-确实是的,对于随机数据来说,时间复杂度约为O(n^3)。但是!!!
如果我们考虑极限数据,把每条边的边权dis[i][j]=i*j
,试试看吧,会怎样?+-+
void bfs(int k)
{
int px,py=0,yy=0,d;
memset(pre,0,sizeof pre);
memset(slack,0x3f,sizeof slack);
linky[py]=k;
do
{
px=linky[py],d=0x3f3f3f3f,vis[py]=1;
for(int i=1;i<=n;i++)
if(vis[i]==0)
{
if(slack[i]>dbx[px]+dby[i]-C[px][i])
slack[i]=dbx[px]+dby[i]-C[px][i],pre[i]=py;
if(slack[i]for(int i=0;i<=n;i++)
if(vis[i]) dbx[linky[i]]-=d,dby[i]+=d;
else slack[i]-=d;
py=yy;
}while(linky[py]!=0);
while(py) linky[py]=linky[pre[py]],py=pre[py];
}
void km()
{
memset(dbx,0,sizeof dbx);
memset(dby,0,sizeof dby);
memset(linky,0,sizeof linky);
for(int i=1;i<=n;i++)
memset(vis,0,sizeof vis),bfs(i);
}
需要注意的是,这里的slack[i]的值所记录的最小差值,并不一定是最后一次根据路径访问到此边时的最小差(即delta),
它很可能是之前的一次访问所产生的差值,这样的前一个点是保存在pre中的,相当于是同时在多条增广路中同时找边。
它的时间复杂度真正实现了O(n^3)。
建议仔细理解含义,实在不行就背下来。