zoukankan      html  css  js  c++  java
  • 【二分图】匈牙利算法,Hopcroft-Karp算法

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

    通过数代人的努力,你终于赶上了剩男剩女的大潮,假设你是一位光荣的新世纪媒人,在你的手上有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);
    }

    参考博文:

    匈牙利算法(二分图) 

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

  • 相关阅读:
    あ 段
    需要注意学习.net过程的要点
    最近因为textview高度问题疯了疯了疯了
    判断是否可以使用麦克风
    tabbar加小红点
    textView富文本点击事件
    通过某一个符号截取字符串
    局部富文本
    判断是否包含某个字符串
    UIProgressView 圆角
  • 原文地址:https://www.cnblogs.com/Kohinur/p/9010751.html
Copyright © 2011-2022 走看看