预备知识基本概念

二分图

定义

  1. 1. 二分图又称作**二部图**,是图论中的一种特殊模型
  2. 1. 若能将无向图 **G=(V,E)** 的顶点 **V** 划分为两个交集为空的顶点集,并且**任意边**的两个**端点都分属于两个集合**,则称图G为一个为二分图
  3. - **解释:****V**所有**顶点集合,E**所有**边集合**

匈牙利算法总结 - 图2

匹配

一个匹配即一个包含若干条边的集合,且其中任意两条边没有公共端点

        - **解释:****匹配集合**就是**所有红边集合,**所有红边的端点不可能是冲突

           ![](https://cdn.nlark.com/yuque/0/2020/png/2955945/1608453786002-a3bb5b75-fc1e-456c-84f4-723b12554c68.png#align=left&display=inline&height=169&margin=%5Bobject%20Object%5D&originHeight=169&originWidth=140&size=0&status=done&style=none&width=140)                ![](https://cdn.nlark.com/yuque/0/2020/png/2955945/1608453791399-7f4b6e25-4c1c-4be4-9d05-94d98cb1b06b.png#align=left&display=inline&height=169&margin=%5Bobject%20Object%5D&originHeight=169&originWidth=140&size=0&status=done&style=none&width=140)

如图,图3的红边即为图2的一个匹配

1.最大匹配

边数最大的子集称为图的最大匹配问题,最大匹配的边数称为最大匹配数

例如 匈牙利算法总结 - 图3 假如你是月老,可以撮合任何一对有暧昧关系的男女,那么你最多能成全多少对情侣最大匹配问题

                    ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2955945/1608454475305-619d32cc-5448-4d61-b8a7-59b6fdf24744.png#align=left&display=inline&height=221&margin=%5Bobject%20Object%5D&name=image.png&originHeight=261&originWidth=224&size=31709&status=done&style=none&width=190)<br />          如图,图4即为**最大匹配图**和**完美匹配图**

2.完美匹配 / 完全匹配 / 最优匹配

完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突
备注:并非每个图都存在完美匹配
其实三个形容词都是指两个集内的所有顶点能够一一匹配,并且所获得的权值最大或最小

**最优匹配又称为带权最大匹配**,是指在带有权值边的二分图中,求一个匹配使得匹配边上的权 值和最大。一般X和Y集合顶点个数相同,最优匹配也是一个完备匹配,即每个顶点都被匹配。 如果个数不相等,可以通过补点加0边实现转化。一般使用KM算法解决该问题。

3.最小覆盖

二分图的最小覆盖分为最小顶点覆盖最小路径覆盖

              1. 最小顶点覆盖:

我们想找到最少的一些,使二分图所有的边都至少有一个端点在这些点中

                    - **备注:**倒过来说就是,**删除包含这些点的边,可以删掉所有边**
                    - 二分图的最小顶点覆盖数 **= **二分图的最大匹配数**(****König定理****)**

                                ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2955945/1608463562002-03682b11-3992-479a-b7a0-708e113f2771.png#align=left&display=inline&height=205&margin=%5Bobject%20Object%5D&name=image.png&originHeight=369&originWidth=299&size=132785&status=done&style=none&width=166)

              1. 最小路径覆盖也称为最小边覆盖:

用尽量少的不相交简单路径覆盖二分图中的所有顶点。

                    - 二分图的最小路径覆盖数 **= **|V|-二分图的最大匹配数

4.最大独立集

最大独立集是指寻找一个点集,使得其中任意两点在图中无对应边

匈牙利算法基本概念

交替路

从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边……形成的路径叫交替路。

增广路

   从一个未匹配的点出发,走交替路,到达了一个未匹配过的点,这条路叫做增广路

                                           ![](https://cdn.nlark.com/yuque/0/2020/png/2955945/1608456138410-a807b5fa-2d59-4675-80a6-15f05372d4db.png#align=left&display=inline&height=168&margin=%5Bobject%20Object%5D&originHeight=318&originWidth=361&size=0&status=done&style=none&width=191)<br />**例子**<br />看图,其中**1、4、5、7****是已经匹配的点**,**1->5 , 4->7是已经匹配的边**<br />那么我们从8开始出发,**8->4->7->1->5->2 **这条交替路就是一条**增广路**

匈牙利算法的流程

一.简单理解

现在我们来看看匈牙利算法是怎么运作的
原则 :**先到先得,能让则让 [ 还能找到女朋有的前提下 ]**

  1. 我们从B1看起( 男女平等,从女生这边看起也是可以的 ),他与G2有暧昧,那我们就先暂时把他与G2连接

    • 备注:这时只是暂时构想,没有真正安排

                                                     ![](https://cdn.nlark.com/yuque/0/2020/jpeg/2955945/1608462044742-c5774a1d-dd96-49c6-9645-f0ef1c2ef8c8.jpeg#align=left&display=inline&height=256&margin=%5Bobject%20Object%5D&originHeight=655&originWidth=509&size=0&status=done&style=none&width=199)
      
  2. 来看B2,B2也喜欢G2,这时G2已经” 名花有主 “了,我们回溯回去看G2目前被安排的男友B1

    • B1能不能换个对象呢?可以换 G4,且 G4 还没有被安排,那我们就让 B1 换成上 G4

                                                       ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2955945/1608462593473-d8965a47-727d-441c-8fd6-ca22b4e1be08.png#align=left&display=inline&height=273&margin=%5Bobject%20Object%5D&name=image.png&originHeight=546&originWidth=470&size=229431&status=done&style=none&width=235)
      
  3. 然后B3,B3直接配上G1就好了,这没什么问题。

                                                       ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2955945/1608462721273-8cf01690-e2f2-4b13-88c5-481c00f0f959.png#align=left&display=inline&height=279&margin=%5Bobject%20Object%5D&name=image.png&originHeight=558&originWidth=438&size=238608&status=done&style=none&width=219)
    
  4. [ 将描述**整个递归过程** ]

   至于**B4**,他喜欢 G4,这时G4已经" **名花有主** "了,**B4** 跑过去问** B1** 你能不能换对象?把G4 让给我 B4 

[ B4询问B1]

  • B1 不知道能不能让对象,跑回去看了看自己的女生备胎,有个G2,但是G2有了男朋友 B2
  • B1 跑过去问 B2,你能不能换对象?把G2 让给我 [ 本着好心人,有备胎的话能让就让 **]**


    [ B1询问B2]

  • B2 也不知道能不能让对象,也跑回去看了看自己的女生备胎,没备胎

  • 对B1 说不行,不能让,让了没对象了 [ 事关自己性福,**不能让 ]**

    • B1 遍历女生备胎,只发现了 G2 ,别人不让给他,所以他也不**能让**,让了他也没女朋友了
    • 最终B4没有女朋友

                                             ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2955945/1608462721273-8cf01690-e2f2-4b13-88c5-481c00f0f959.png#align=left&display=inline&height=279&margin=%5Bobject%20Object%5D&name=image.png&originHeight=558&originWidth=438&size=238608&status=done&style=none&width=219)<br />** 最终最大匹配结果图 **<br />**上述示例的验证代码**
      
      # include<iostream>
      using namespace std;
      #define maxM 5
      #define maxN 5
      int M, N;            //M, N分别表示左、右侧集合的元素数量
      int Map[maxM][maxN] = {
      {0,0,0,0,0},
      {0,0,1,0,1},
      {0,0,1,0,0},
      {0,1,0,1,0},
      {0,0,0,0,1},
      }; //邻接矩阵存图
      int p[maxN];         //记录当前右侧元素所对应的左侧元素
      bool vis[maxN];      //记录右侧元素是否已被访问过
      bool match(int i) {
      for (int j = 1; j <= N; ++j)
       if (Map[i][j] && !vis[j]) {//有边且未访问
           vis[j] = true;        //记录状态为访问过
           if (p[j] == 0 || match(p[j])) { //如果右侧暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
               p[j] = i;    //当前左侧元素成为当前右侧元素的新匹配
               return true; //返回匹配成功
           }
       }
      return false; //循环结束,仍未找到匹配,返回匹配失败
      }
      int Hungarian() {
      int cnt = 0;
      for (int i = 1; i <= M; ++i) {
       memset(vis, 0, sizeof(vis)); //重置vis数组
       if (match(i))
           cnt++;
      }
      return cnt;
      }
      int main(void) {
      M = N = 4;
      cout << Hungarian() << endl;
      return 0;
      }
      

      二.深入理解

由增广路的性质,增广路中的匹配边总是比未匹配边多一条,所以如果我们放弃一条增广路中的匹配边选取未匹配边作为匹配边,则匹配的数量就会增加
匈牙利算法就是在不断寻找增广路,如果找不到增广路,就说明达到了最大匹配。

例子

1、起始没有匹配
匈牙利算法总结 - 图4
2、选中第一个 x1 点找第一个 y1连线 **
匈牙利算法总结 - 图5

3、选中第二个 x2 点找第二个 y2连线
匈牙利算法总结 - 图6
4、选中第三个 x3 点,发现 x3 的第一条边 x3 - y1 已经被人占了

  • 解决措施:
    • 找出 x3 出发的的交错路径 **x3-y1-x1-y4**,把交错路中已在匹配上的边 **x1-y1从匹配中去掉**
    • 将剩余的边x3-y1-x1-y4 **加到匹配中 **

image.png
5、选中第四个 x4 点找第四个 y3连线
6、同理加入 x5 ****x6
image.png
总结:

  1. 上述是针对的深度优先
  2. 匈牙利算法可以针对深度优先也可以针对广度优先
    • 解释:如刚才的 **x3** 点冲突
    • 深度优先 x3 **y1 , y1 已经有匹配,则找交错路**
    • 广度优先 x3y1 , y1 已经有匹配,则 x3** y2,y3,y4,y5,y6 **直至匹配

      匈牙利算法的代码**

      ```cpp /**
  • 参数说明 :
  • M变量 : 左集合的元素数量 ——-男朋友
  • N变量 : 右集合的元素数量 ——-女朋友
  • Map数组 : 邻接矩阵存图,存储路径关系
  • p数组 : 记录当前右侧元素所对应的左侧元素—-存储女的男朋友
  • vis数组 : 记录右侧元素是否已被访问过 —-存储女的有没有男朋友 */ int M, N; //M, N分别表示左、右侧集合的元素数量 int Map[MAXM][MAXN]; //邻接矩阵存图 int p[MAXN]; //记录当前右侧元素所对应的左侧元素 bool vis[MAXN]; //记录右侧元素是否已被访问过 bool match(int i){ for (int j = 1; j <= N; ++j) if (Map[i][j] && !vis[j]) {//有边且未访问
     vis[j] = true;        //记录状态为访问过
     if (p[j] == 0 || match(p[j])){ //如果右侧暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
         p[j] = i;    //当前左侧元素成为当前右侧元素的新匹配
         return true; //返回匹配成功
     }
    
    } return false; //循环结束,仍未找到匹配,返回匹配失败 } int Hungarian(){ int cnt = 0; for (int i = 1; i <= M; ++i){ memset(vis, 0, sizeof(vis)); //重置vis数组 if (match(i))
     cnt++;
    
    } return cnt; } ```