【二分图】匈牙利算法,Hopcroft-Karp算法

Posted on 2018-05-08 20:30  som_nico  阅读(837)  评论(0编辑  收藏  举报

先通过一个有趣的例子了解一下二分图的大致意思:

通过数代人的努力,你终于赶上了剩男剩女的大潮,假设你是一位光荣的新世纪媒人,在你的手上有N个剩男,M个剩女,每个人都可能对多名异性有好感(惊讶-_-||暂时不考虑特殊的性取向),如果一对男女互有好感,那么你就可以把这一对撮合在一起,现在让我们无视掉所有的单相思(好忧伤的感觉快哭了),你拥有的大概就是下面这样一张关系图,每一条连线都表示互有好感。

 

本着救人一命,胜造七级浮屠的原则,你想要尽可能地撮合更多的情侣,匈牙利算法的工作模式会教你这样做:

===============================================================================

一: 先试着给1号男生找妹子,发现第一个和他相连的1号女生还名花无主,got it,连上一条蓝线

 

===============================================================================

二:接着给2号男生找妹子,发现第一个和他相连的2号女生名花无主,got it

 

===============================================================================

三:接下来是3号男生,很遗憾1号女生已经有主了,怎么办呢?

我们试着给之前1号女生匹配的男生(也就是1号男生)另外分配一个妹子。

 

(黄色表示这条边被临时拆掉)

 

与1号男生相连的第二个女生是2号女生,但是2号女生也有主了,怎么办呢?我们再试着给2号女生的原配(发火发火)重新找个妹子(注意这个步骤和上面是一样的,这是一个递归的过程)

 

 

此时发现2号男生还能找到3号女生,那么之前的问题迎刃而解了,回溯回去

 

2号男生可以找3号妹子~~~                  1号男生可以找2号妹子了~~~                3号男生可以找1号妹子

所以第三步最后的结果就是:

 

===============================================================================

四: 接下来是4号男生,很遗憾,按照第三步的节奏我们没法给4号男生腾出来一个妹子,我们实在是无能为力了……香吉士同学走好。

 

===============================================================================

这就是匈牙利算法的流程,其中找妹子是个递归的过程,最最关键的字就是“腾”字

其原则大概是:有机会上,没机会创造机会也要上

 

基本概念二分图

1 二分图

设G=(V, E)是一个无向图。如果顶点集V可分割为两个互不相交的子集X和Y,并且图中每条边连接的两个顶点一个在X中,另一个在Y中,则称图G为二分图。

2 匹配

M包含于E(G),任意ei,ej∈M,ei,ej不存在公共顶点,则M是图G的一个匹配。
M中的每个端点称为被M匹配。

3 完美匹配

G中的每个顶点都被M匹配了,则称M是G的一个完美匹配。

4 匹配的大小

M中边的条数,记为|M|

5 极大匹配

每次选择一条边使得其端点没有被已经选出的边用过,直到没有可选的边为止。(极大匹配不是最大匹配)


6 交错路径和增广路径

如果一条路径的边交替出现在M中和不出现在M中,则称这条路径为一条M-交错路径。
路径的起始点和终点未被M匹配的M-交错路径称为M-增广路径。

 

7 berge定理

对称差:A△B=(A-B)∪(B-A)
G=(V,E)的任意两个匹配M,M'的对称差,其分量或者是一条路径,或者是一个偶环。
berge定理:M是G的最大匹配,当且仅当G中不存在M-增广路径。

 

匈牙利算法

(具体思想看上面找女朋友的例子,这个是匈牙利算法的DFS表现)

#define maxn 10//表示x集合和y集合中顶点的最大个数!
 int nx,ny;//x集合和y集合中顶点的个数
 int edge[maxn][maxn];//edge[i][j]为1表示ij可以匹配
 int cx[maxn],cy[maxn];//用来记录x集合中匹配的y元素是哪个!
 int visited[maxn];//用来记录该顶点是否被访问过!
 int path(int u)
 {
     int v;
     for(v=0;v<ny;v++)
     {
         if(edge[u][v]&&!visited[v])
         {
             visited[v]=1;
            if(cy[v]==-1||path(cy[v]))//如果y集合中的v元素没有匹配或者是v已经匹配,但是从cy[v]中能够找到一条增广路
             {
                 cx[u]=v;
                 cy[v]=u;
                 return 1;
             }
         }
     }
     return 0;
 }
 int maxmatch()
 {
     int res=0;
     memset(cx,0xff,sizeof(cx));//初始值为-1表示两个集合中都没有匹配的元素!
     memset(cy,0xff,sizeof(cy));
     for(int i=0;i<=nx;i++)
     {
         if(cx[i]==-1)
         {
             memset(visited,0,sizeof(visitited));
             res+=path(i);
         }
     }
     return res;
 }

 时间复杂度:匈牙利算法每次寻找增广路径的时间复杂度是O(m) 最多需要寻找O(n)次,所以复杂度是O(nm)

 

Hopcroft-Karp算法

该算法由John.E.Hopcroft和Richard M.Karp于1973提出,故称Hopcroft-Karp算法。

原理

为了降低时间复杂度,可以在增广匹配集合M时,每次寻找多条增广路径。这样就可以进一步降低时间复杂度,可以证明,算法的时间复杂度可以到达O(n^0.5*m),虽然优化不了多少,但在实际应用时,效果还是很明显的。

 

/**dx[i]表示左集合i顶点的距离编号,dy[i]表示右集合i顶点的距离编号**/
/**mx[i]表示左集合顶点所匹配的右集合顶点序号,my[i]表示右集合i顶点匹配到的左集合顶点序号。**/

struct edge {
    int v,next;
}e[Mm];
int tot,head[Mn];
void addedge(int u,int v) {
    e[tot].v=v;
    e[tot].next=head[u];
    head[u]=tot++;
}
int mx[Mn],my[Mn],vis[Mn];
int dis;
int dx[Mn],dy[Mn];
int n,m;
bool searchp() {
    queue<int>q;
    dis=INF;
    CLR(dx,-1);
    CLR(dy,-1);
    for(int i=1;i<=n;i++) {
        if(mx[i]==-1) {
            q.push(i);
            dx[i]=0;
        }
    }
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        if(dx[u]>dis) break;
        for(int i=head[u];~i;i=e[i].next) {
            int v=e[i].v;
            if(dy[v]==-1) {
                dy[v]=dx[u]+1;
                if(my[v]==-1) dis=dy[v];
                else {
                    dx[my[v]]=dy[v]+1;
                    q.push(my[v]);
                }
            }
        }
    }
    return dis!=INF;
}
bool dfs(int u) {
    for(int i=head[u];~i;i=e[i].next) {
        int v=e[i].v;
        if(vis[v]||(dy[v]!=dx[u]+1)) continue;
        vis[v]=1;
        if(my[v]!=-1&&dy[v]==dis) continue;
        if(my[v]==-1||dfs(my[v])) {
            my[v]=u;
            mx[u]=v;
            return true;
        }
    }
    return false;
}
int maxMatch() {
    int res = 0;
    CLR(mx,-1);
    CLR(my,-1);
    while(searchp()) {
        CLR(vis,0);
        for(int i=1;i<=n; i++)
            if(mx[i] == -1 && dfs(i))
                res++;
    }
    return res;
}
void init() {
    tot=0;
    CLR(head,-1);
}

 

 

 

参考博文:

匈牙利算法(二分图) 

趣写算法系列之--匈牙利算法