zoukankan      html  css  js  c++  java
  • 小撸--二分图

    讲二分图 之前 我先提下关于它的各种基础概念  在了解一个新的算法 应该有必要将关于它的概念 有所了解

    1.什么是二分图?

    :二分图 又叫做二部图 是图论中的一种特殊模型 设G=(V,E)是一个无向图 如果顶点V可以分割为两个互不相交的子集(A,B) 并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这个不同的顶点集(i in A j in B) 则称G为一个二分图(这是官方的定义 感觉也是最详细的)

    2.什么是匹配?

    匹配是 基于二分图上提出的概念。 给定一个二分图G  M为G边集的一个子集 如果M满足当中的任意两条边都不依附于同一个顶点 则称M是一个匹配

    3.什么是二分图 极大匹配?

    是指在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数

    4.什么是二分图 最大匹配?

    所有极大匹配当中边数最大的一个匹配   同通俗来说就是----在匹配的基础上选择边数最大的子集称为最大匹配

    5.增广路(增广轨 交错轨)是什么?

    若P是图G中一条连通两个未匹配顶点的路径 并且属于M的边喝不属于M的边(即已匹配和未匹配的边)在P上交替出现 则称P为相对于M的一条增广路径
    (sample: 有A B集合 增广路由A中一个点通向B中一个点 再由B中这个点通向A中一个点 就是这样交替进行下去.)

    由增广路的定义引申出的三个结论:
    1.P的路径长度必定为奇数 第一条边喝最后一条边都不属于M
    2.不断寻找增广路可以得到一个更大的匹配M‘ 直到找不到更多的增广路
    3.M为G的最大匹配当且仅当不存在M的增广路径

    6.最小点覆盖数 是什么?

    即要求用最少的点(A B任意一集合)让每条边至少和其中一个点相关联
    可以证明: 最小点覆盖数 == 最大匹配数(证明过程这里不给出 希望读者自行查阅)

    7.独立集是什么?

    图G中两两互不相邻的顶点构成的集合

    可以证明: 最大独立集 == 顶点数 - 二分图最大匹配(同上 证明过程不予给出 请自行查阅)

    8.最小路径覆盖是什么?

    简而言之-就是找出最小的路径条数 使之成为P的一个路径覆盖
    一个PXP的有向图中 路径覆盖就是在图中找一些路径使之覆盖了图中所有的顶点 且任何一个顶点有且只有一条路径与之关联(如果把这些路径中的每条路径从它们起始点走到它的终点 那么恰好经过图中的每个顶点一次仅且一次) 如果不考虑图中存在回路 那么每条路径就是一个弱连通子集

    得出的结论:
    1.一个单独的顶点是一条路径
    2.如果存在一路径p1,p2,......pk 其中p1 为起点 pk为终点 那么在覆盖图中顶点p1,p2,......pk不再与其它的顶点之间存在有向边

    可以证明: 最小路径覆盖 == |P|-二分图最大匹配 (同上)

    看完上面这么一大堆概念 是不是和我一样头晕了。。。  还是先去撸一把吧?

    继续... 这里我们给出一道例题来更好的理解:

    题目链接 : http://acm.nyist.net/JudgeOnline/problem.php?pid=239

    这题 应该是最简单的 二分图最大匹配应用题 题意也很好转换 即是求 二分图最大匹配

    同样 对于图的信息存储 这里同样可以用邻接表 或 邻接矩阵

    先去上课了~~

     耽搁了一天了  继续。。。。

    这边 我介绍一种 二分图中常用的经典算法---匈牙利算法  还有一种算法是-Hopcroft-Carp 它的时间复杂度比前者更小 更适合大数据

    它的实现方式还是很简单的 如果 有2个顶点集 分别为A  B

    我们只需要通过遍历A中的所有顶点 将它与B集合进行匹配 这个过程 我们可以通过DFS来实现。

    因为 它的代码很容易理解 除了其中的反向查找增广路的代码片段 但是 你可以自己在纸上模拟演算下 会便于你理解很多

    我这边给出关于它的 邻接表 与 邻接矩阵的实现方式 并附上注释

    这边 值得一提的是: 我用邻接矩阵 TLE   可能是写的太渣了吧 如果你AC了  不妨留言告诉我。

     1 #include <cstdio>
     2 #include <cstring>
     3 using namespace std;
     4 
     5 #define num 520
     6 int n;
     7 int matrix[num][num];//建立邻接矩阵来存储信息
     8 bool vis[num];//标记点是否被访问过
     9 int linker[num];//标记该点的前一个节点是那个
    10 
    11 bool dfs(int u)//从U集合开始向V集合寻找匹配
    12 {
    13     for(int v=1;v<=n;v++)
    14     {
    15         if( !vis[v] && matrix[u][v] )//V集合的点与U集合该点有边相邻 并且这个点不能被访问过
    16         {
    17             vis[v]=true;
    18             if( !linker[v] || dfs( linker[v] ) )//反向找增广路 如果能成功 就可以使匹配边+1
    19             {
    20                 linker[v]=u;
    21                 return true;
    22             }
    23         }
    24     }
    25     return false;
    26 }
    27 
    28 int getSum()
    29 {
    30     int count=0;
    31     memset( linker,0,sizeof(linker) );// 这里 与下面不同 只需要初始化一次 
    32     for(int u=1;u<=n;u++)
    33     {
    34         memset( vis , false , sizeof(vis) );//这里 注意 每一次A集合中的顶点去与B集合去匹配的过程 都需要更新点的访问
    35         if( dfs(u) )
    36         {
    37             count++;
    38         }
    39     }
    40     return count;
    41 }
    42 
    43 int main()
    44 {
    45     int t;
    46     int m;
    47     int x,y;
    48     while(~scanf("%d",&t))
    49     {
    50         while(t--)
    51         {
    52             scanf("%d %d",&n,&m);
    53             for(int i=0;i<=n;i++)
    54             {
    55                 for(int j=0;j<=n;j++)
    56                 {
    57                     matrix[i][j]=0;
    58                 }
    59             }
    60             while(m--)
    61             {
    62                 scanf("%d %d",&x,&y);
    63                 matrix[x][y]=matrix[y][x]=1;//无向图 2点相邻
    64             }
    65             printf("%d
    ",getSum() );
    66         }
    67     }
    68 }
    View Code
     1 #include <cstdio>
     2 #include <cstring>
     3 #include <vector>
     4 using namespace std;
     5 
     6 //如果你不清楚 下面有关vector的函数 请去查询资料 这是一个非常重要的容器 并且它的作用真的很大
     7 #define num 520
     8 vector<int>mp[num];//邻接表存储
     9 int linker[num];//记录该点的前驱.
    10 bool vis[num];//标记是否被访问
    11 int n;
    12 
    13 void inital(int n)//这里不能遗忘 每次需要清空邻接表
    14 {
    15     for(int i=1;i<=n;i++)
    16     {
    17         mp[i].clear();
    18     }
    19 }
    20 bool dfs(int u)
    21 {
    22     for(int i=0;i<mp[u].size();i++)//第u行共有的元素数量
    23     {
    24         if( !vis[ mp[u][i] ] ) //该点未被匹配
    25         {
    26             vis[ mp[u][i] ]=true; //标记
    27             if( !linker[ mp[u][i] ] || dfs( linker[ mp[u][i] ] ) )
    28             {
    29                 linker[ mp[u][i] ]=u; //找增广路 反向
    30                 return true;
    31             }
    32         }
    33     }
    34     return false;
    35 }
    36 
    37 int getSum()
    38 {
    39     int count=0;
    40     memset( linker ,0, sizeof(linker) );
    41     for(int u=1; u<=n ;u++ )
    42     {
    43         memset( vis ,false, sizeof(vis) );
    44         if( dfs(u) )
    45         {
    46             count++;
    47         }
    48     }    
    49     return count;
    50 }
    51 
    52 int main()
    53 {
    54     int t,m;
    55     int x,y;
    56     while(~scanf("%d",&t))
    57     {
    58         while(t--)
    59         {
    60             scanf("%d %d",&n,&m);
    61             inital(n);
    62             while(m--)
    63             {
    64                 scanf("%d %d",&x,&y);
    65                 mp[x].push_back(y);//这里 只需要将Y顶点压入X顶点即可
    66             }
    67             printf("%d
    ",getSum() );
    68         }
    69     }
    70 }
    View Code


    至于 上文提过的 另外一种算法 等我掌握了 再来写。。

    long long way to go

    just follow your heart
  • 相关阅读:
    Java实现 蓝桥杯 生命游戏
    Java实现 蓝桥杯 生命游戏
    Java实现UVA10131越大越聪明(蓝桥杯每周一题)
    Linux 静态库&动态库调用
    linux下c++开发环境安装(eclipse+cdt)
    怎样在Windows和Linux下写相同的代码
    教会你如何编写makefile文件
    Linux编译多个不同目录下的文件以及静态库、动态库的使用
    Fedora 17 下安装codeblocks
    Fedora 下安装codeblocks
  • 原文地址:https://www.cnblogs.com/radical/p/3666783.html
Copyright © 2011-2022 走看看