二分图的最大匹配问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jeryjeryjery/article/details/79596922

1.定义

二分图:
  将一个图的所有顶点划分为两个不相交集U和V,使得图中的每一条边的顶点分别属于点集合U和点集V,即同一点集中的点不构成边,这样的图叫做二分图。维基百科中给出的无向图G的二分图的充分必要条件是:G至少有两个顶点,且其所有回路的长度均为偶数。下图为带有回路的二分图举例:
这里写图片描述

匹配:
  图中匹配的定义是指,这个图的一个边的集合,集合中任意两条边都没有公共的顶点,则称这个边集为一个匹配。我们称匹配中的边为匹配边,边中的点为匹配点;未在匹配中的边为非匹配边,边中的点为未匹配点

最大匹配:
  一个图中所有匹配中,所含匹配边数最大的匹配称为最大匹配。

完美匹配:
  如果一个图的某个匹配中,图的所有顶点都是匹配点,那么这个匹配就是完美匹配。很显然,完美匹配一定是最大匹配,但是并不是所有的图都存在完美匹配。
这里写图片描述
Fig.2是某个二分图。Fig.3是该二分图中的某个匹配,匹配中的边用红色标明了。Fig.4是该二分图的某个最大匹配,同时也是完美匹配。

2.图的最大匹配问题

  我们知道了图的最大匹配问题,那么如何求解图的最大匹配呢?这时候就得使用匈牙利算法。讲解算法之前,先讲解算法中的一些概念的定义。

交替路:
  从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边、匹配边…,形成这样的交替进行的路径成为交替路。

增广路:
  从一个未匹配点出发,走交替路,如果途径一个未匹配点(出发点不算),则这样一条交替路称为增广路。增广路有一个重要的特性,就是非匹配边要比匹配边多一条(从未匹配点出发,以未匹配点结束,走交替路,显然非匹配边多一条),此时,我们可以让增广路中的匹配边和非匹配边调换位置,匹配边变为非匹配边,非匹配边变为匹配边,这样匹配边的数量就会加1,并且由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质,保证了交换的正确性。

匈牙利算法:
  算法就是根据增广路的思想,以一个未匹配的节点出发,遍历图,不断的寻找增广路来扩充匹配的边数,直到不能扩充为止。根据遍历图的方式不同,匈牙利算法可以分为dfs(深度遍历)和bfs(广度遍历)的实现。

以上面讲解匹配的图为例,其匈牙利算法实现的c语言代码如下:

#include<bits/stdc++.h>
using namespace std;

int graph[20][20];                                   //图的大小
int n;                                               //节点数
int visit[20];                                       //是否访问
int matched[20];                                     //是否已经匹配,对应的匹配点

//显示匹配结果
void show(){
    memset(visit, 0, sizeof(visit));

    for(int i = 0; i < n; i++){
        if(!visit[i]){
            if(matched[i] != -1){
                cout<<"("<<(i + 1)<<", "<<(matched[i] + 1)<<")"<<endl;
                visit[i] = 1;
                visit[matched[i]] = 1;
            }
        }
    }
}

/*
*   dfs实现,
*   params:
*       x:起始的未匹配点
*   return:
*       1:找到增广路
*       0:未找到
*/
int dfs_solve(int x){
    //找到一个和节点存在边的点,并且该点在本轮中没有被访问过
    for(int i = 0; i < n; i++){
        if(graph[x][i] && !visit[i]){
                visit[i] = 1;                         //标记为匹配过
            //按照交替路的模式找增广路,增广路相对于交替路的特性是就是,第一个节点和最后一个节点都是未匹配过的节点
            if(matched[i] == -1 || dfs_solve(matched[i]) == 1){           //直接跳到matched[i]能够保证匹配边和未匹配边交替
                //说明找到了一个未匹配节点,将所有匹配边变为未匹配边,将所有未匹配边变为匹配边,这样匹配边数会加1,这个交换过程通过回溯实现

                matched[x] = i;
                matched[i] = x;
                cout<<(x+1)<<" 和 "<<(i+1)<<" 匹配"<<endl;
                return 1;
            }
        }
    }
    return 0;
}

/*
*   dfs实现,
*   params:
*       x:起始的未匹配点
*   return:
*       num:0表示未找到增广路,1表示找到
*/
int hungarian1(){
    memset(matched, -1, sizeof(matched));
    int sum = 0;

    for(int i = 0; i < n; i++){
        if(matched[i] == -1){
            cout<<endl;
            cout<<"从 "<<(i + 1)<<" 开始匹配"<<endl;
            memset(visit, 0 , sizeof(visit));
            sum += dfs_solve(i);
        }
    }

    cout<<"共有 "<<sum<<" 匹配项"<<endl;
    show();

    return sum;
}

//匈牙利算法,bfs实现
int bfs_solve(int x){
    queue<int> items;
    int prev[20];
    int num = 0;

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

    items.push(x);
    bool flag = 0;
    while(!flag && items.size()){
        int u = items.front();
        items.pop();

        for(int i = 0; i < n; i++){
            if(graph[u][i] && !visit[i] && !flag){
                visit[i] = 1;

                if(matched[i] == -1){                           //找到一个未匹配点
                    flag = 1;                                   //交换匹配路径和未匹配路径,不能用回溯,只能向前找祖先

                    int l = u, r = i;

                    while(l != -1){
                        int tmp = matched[l];

                        matched[l] = r;
                        matched[r] = l;

                        cout<<(l+1)<<" 和 "<<(r+1)<<" 匹配"<<endl;

                        r = tmp;
                        l = prev[l];
                    }
                    num++;
                }
                else{                                           //点不是未匹配点
                    prev[matched[i]] = u;
                    items.push(matched[i]);                     //只有找到了匹配边才能压入端点,保证交替进行
                }
            }
        }
    }
    return num;
}

int hungarian2(){
    memset(matched, -1, sizeof(matched));
    int sum = 0;

    for(int i = 0 ; i < n; i++){
        if(matched[i] == -1){
            cout<<endl;
            cout<<"从 "<<(i + 1)<<" 开始匹配"<<endl;

            memset(visit, 0, sizeof(visit));
            sum += bfs_solve(i);
        }
    }

    cout<<"共有 "<<sum<<" 匹配项"<<endl;
    show();

    return sum;
}

int main()
{
    memset(graph, 0, sizeof(graph));

    graph[0][4] = 1;
    graph[0][6] = 1;

    graph[1][4] = 1;

    graph[2][4] = 1;
    graph[2][5] = 1;

    graph[3][6] = 1;
    graph[3][7] = 1;

    graph[4][0] = 1;
    graph[4][1] = 1;
    graph[4][2] = 1;

    graph[5][2] = 1;

    graph[6][0] = 1;
    graph[6][3] = 1;

    graph[7][3] = 1;

    n = 8;

    hungarian1();

    cout<<"**********************************"<<endl;

    hungarian2();


    return 0;
}

猜你喜欢

转载自blog.csdn.net/jeryjeryjery/article/details/79596922