二分图总结(详细图文适合小白)

刚学完二分图感觉总结一下比较好,图论确实让人头秃,差不多一个多星期大概理解了二分图的内容,但还是挺生涩,还是多打点题吧
由于第一次学可能有些地方有出错欢迎大家指正!

大纲二分图总结(详细图文适合小白)_第1张图片

概念汇总

一、二分图的定义

二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。(简单说就是把一个图的顶点分成两个集合,且集合内的点不邻接)

二、匹配

1.匹配:给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配
2.极大匹配:指在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数。
3.最大匹配:所有极大匹配当中边数最大的一个匹配。选择这样的边数最大的子集称为图的最大匹配问题
4.完全匹配(完备匹配):一个匹配中,图中的每个顶点都和图中某条边相关联。

二分图的判断

一、理论判断

  • 如果某个图为二分图,那么它至少有两个顶点,且其所有回路的长度均为偶数,任何无回路的的图均是二分图。

个人理解:两个顶点很好理解,毕竟一个点也不能配对。无回路也可以很容易把点分成两个无关的集合。有回路时,如果回路为奇数
(图2)无论如何不能分成两个无关联的集合,故不是二分图,偶数则可以(图1)。
二分图总结(详细图文适合小白)_第2张图片

二、程序判断(染色法)

  • 用两种颜色,对所有顶点逐个染色,且相邻顶点染不同的颜色,如果发现相邻顶点染了同一种颜色,就认为此图不为二分图。
    当所有顶点都被染色,且没有发现同色的相邻顶点,就退出,此图是二分图。

    个人理解:其实和理论判断的原理是一样的,巧妙地将回路的奇偶转换成颜色的异同。另外,需要注意的是,若没说明是连通图,需要遍历全部顶点。

下面用邻接表法分别给出dfs和bfs的代码

bfs法
const int maxn = 1e5+10;
vector <int> G[maxn];
int col[maxn];
bool bfs(int u)//这里因为不一定连通图的原因设置一个变量,外部引用函数的时候遍历访问就行
{
    queue<int> q;
    q.push(u);//当前点入队
    col[u]=1;//当前点涂色
    while(!q.empty())
    {
        int v=q.front();
        q.pop();
        for(int i=0;i<G[v].size();i++)//遍历与当前点关联的所有点
        {
            int x=G[v][i];//获得第i个关联点的下标
            if(col[x]==0)//如果没图色
            {
                col[x]=-col[v];//图相反颜色
                q.push(x);
            }
            else
            {
                if(col[x]==col[v])//颜色相同不是二分图返回false
                    return false;
            }
        }
    }
    return true;
}

void solve()
{
    for(int i=1;i<=n;i++)
    {
       if(col[i]==0&&!bfs(i))
       {
           printf("No\n");
           return;
       }
    }
    printf("Yes\n");
}

dfs法
bool dfs(int v, int c){
    color[v] = c;    //将当前顶点涂色
    for(int i = 0; i < n; i++){    //遍历所有相邻顶点,即连着的点
        if(edge[v][i] == 1){    //如果顶点存在
            if(color[i] == c)    //如果颜色重复,就返回false
                return false;
            if(color[i] == 0 && !dfs(i,-c))    //如果还未涂色,就染上相反的颜色-c,并dfs这个顶点,进入下一层
                return false;   //返回false
        }
    }
    return true;   //如果所有顶点涂完色,并且没有出现同色的相邻顶点,就返回true
}

最大匹配

一、概念

  • 交替路:从一个未匹配的点出发,依次经过未匹配边、匹配边、未匹配边…这样的路叫做交替路。
  • 增广路:从一个未匹配的点出发,走交替路,到达了一个未匹配过的点,这条路叫做增广路(图3)。
    二分图总结(详细图文适合小白)_第3张图片

红线代表匹配边, 1、5、4、7已匹配,则8->4->7->1->5为一条增广路

二、匈牙利算法

  • 原理:匈牙利算法通过不断求增广路来求最大匹配。因为增广路有下面四个性质:

    1.增广路有奇数条边 。
    2.路径上的点一定是一个在X边,一个在Y边,交错出现。
    3.起点和终点都是目前还没有配对的点。
    4.未匹配边的数量比匹配边的数量多1。
    主要是性质4,由于未匹配边数量比匹配边多1,故只要有增广路那么把这条路的匹配边变成未匹配边,未匹配边变成匹配边那么总匹配边数就能加1。
    贴上一个生动形象的博客:四个男人和四个女人的故事

  • 代码

const int maxn = 1e5+10;

vector <int> G[maxn];
int n,m;//n为点的数量 m为边的数量
int vis[maxn],pei[maxn];//vis记录点是否被访问,pei存每个点所匹配的点

bool dfs(int u)
{
    for(int i=0;i<G[u].size();i++)//遍历与该点关联的所有点
    {
        int v=G[u][i];//所关联的点
        if(!vis[v])//没访问过
        {
            vis[v]=1;
            if(!pei[v]||dfs(pei[v]))//若关联的点没被匹配或者dfs返回true说明关联的点所匹配的点可以挪走,则把关联的点和传入的点匹配(即下面代码)
            {
                pei[v]=u;
                return true;
            }
        }
    }
    return false;
}

void solve()
{
    int ans=0;
    for(int i=1;i<=n;i++)//遍历所有的点
    {
        memset(vis,0,sizeof(vis));
        if(dfs(i)) ans++;//如果有增广路则加一条匹配边
    }
    printf("%d\n",ans);
}

模板题

Fire Net HDU - 1045 这题还用到了缩点的原理 需要思考一下
The Accomodation of Students HDU - 2444 很裸的判断二分图+最大匹配

你可能感兴趣的:(数据结构)