欧拉路径,欧拉回路,并查集

1.1 定义
对于图G,若存在一条路径,经过G中每条边有且仅有一次,称这条路为欧拉路径;如果存在一条回路经过G每条边有且仅有一次,称这条回路为欧拉回路。具有欧拉回路的图成为欧拉图。

1.2 判断欧拉路径是否存在的方法

有向图:图连通,且只有一个顶点出度大入度1,有一个顶点入度大出度1,其余都是出度=入度。
无向图:图连通,奇度数的结点个数不多于2个,其余都是偶数度的。

1.3 判断欧拉回路是否存在的方法

有向图:图连通,且所有的顶点出度=入度。
无向图:图连通,且所有顶点都是偶数度。

定理一:如果连通无向图 G 有 2k 个奇顶点(奇定点数必然为偶数个),那么它可以用 k 笔画成,并且至少要用 k 笔画成。
证明:将这 2k 个奇顶点分成 k 对后分别连起,则得到一个无奇顶点的连通图。由上知这个图是一个环,因此去掉新加的边后至多成为 k 条欧拉路径,因此必然可以用 k 笔画成。但是假设全图可以分为 q 条欧拉路径,则由定理一知,每条链中只有不多于两个奇顶点,于是 2q \ge 2k。因此必定要 k 笔画成。

1.4 欧拉路径与欧拉回路的题目
程序实现一般是如下过程:
(1).利用并查集判断图是否连通,即判断p[i] < 0的个数,如果大于1,说明不连通。
(2).根据出度入度个数,判断是否满足要求。
(3).利用DFS输出路径。

P1: 给出N个节点,M个边,问要遍历一遍所有的边,需要的最小group数目。即相当于求一个图中最少需要几笔画.
(代码源自http://www.cnblogs.com/buptLizer/archive/2012/04/15/2450297.html , 此处对原作者代码做了分析,以便思路更明确。)


/*函数过程中是维护一个连通图点集A,初始时,每个单独的结点都是一个连通图,每加入一条边,则将两个连通图合并在一起,组成一个新的连通图。且对于每个连通图,都有其代表结点x。数组p[]用来记录结点与所在连通图代表结点之间的关系。*/

#include <iostream>
#include <stdio.h>
using namespace std;
const int maxv = 100 + 2;
int odds[maxv];                 /* 顶点 */
int du[maxv];                   /* 每个顶点的度数 */
int p[maxv];                    /* 并查集数组 */
bool used[maxv];
int scc[maxv];                  /* scc个数 */
void init(int n)                /*初始化数组*/
{
    for(int i = 0; i <= n; ++i)
    {
        odds[i] = 0;
        p[i] = -1;
        du[i] = 0;
        used[i] = 0;
    }
}
int utf_find(int x)     /*本函数用来寻找结点x的代表结点。如果x尚未加入到数组当中或者x即 */
{                       /* 为自己以及其他结点的代表结点,p[x]<0. 此时返 回自己x;*/
    if(0 <= p[x])       /*若结点x已被加入到集合A中且此时x的代表结点不是自己*/
    {                   /* 则递归调用寻找所在连通图的代表结点x',特征是p[x']<0。*/
        p[x] = utf_find(p[x]);
        return p[x];
    }
    return x;
}
void utf_union(int a, int b)  //当加入一条边的时候,是合并两个连通图。
{
    int r1 = utf_find(a);     //寻找新加入边的两个结点所在连通图的代表结点是否一样,
    int r2 = utf_find(b);     //一样的话则为“冗余边”;否则即是将两个连通图连通在一块。
    if(r1 == r2)
        return;
    int n1 = p[r1];           //每个连通图中结点的个数,连通图A1中的结点个数为|n1|,连通图A2中的结点个数为|n2|.
    int n2 = p[r2];		
    if(n1 < n2)               //A1中结点个数大于A2中结点个数
    {                         //将A2中的代表结点,再链接到A1中的代表结点。
        p[r2] = r1;           //这样A1,A2中结点的代表结点将都是A1的代表结点。
        p[r1] += n2;          //实现两个连通图的合并.更新新的连通图A1中的结点个数。
    }
    else
    {
        p[r1] = r2;           //同上。
        p[r2] += n1;
    }
}
int main()
{
    int n = 0;
    int m = 0;
    int a = 0;
    int b = 0;
    int i = 0;
    int cnt = 0;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        init(n);
        cnt = 0;
        for(i = 1; i <= m; ++i)        //本段为加入边,并对边做并查集归并。具体如上所示。
        {
            scanf("%d%d", &a, &b);
            du[a]++;
            du[b]++;
            utf_union(a, b);
        }
        for(i = 1; i <= n; ++i)
        {
            int f = utf_find(i);
            if(!used[f])              //检查图中的连通图个数
            {
                used[f] = 1;
                scc[cnt++] = f;
            }
            if(1 == du[i]%2)          //检查每个连通图中度数为奇数的结点个数
                odds[f]++;
        }
        int ret = 0;
        for(i = 0; i < cnt; ++i)      //对每个连通图做判断
        {
            if(0 == du[scc[i]])       //如果连通图是单个结点,则继续。
                continue;
            if(0 == odds[scc[i]])     //否则如果连通图中度数为为奇数的结点个数为0,则只需
                ++ret;                //一笔或者或者只需一条路径就能遍历所有的边
            else                      //若连通图中度数为奇数的节点个数不为0,
                ret += odds[scc[i]]/2;//设为2k,则需要k条路径才能遍历所有的边。
        }
        printf("%d\n", ret);          //函数最后返回遍历所有的边需要的路径数
    }
    return 0;
}

P2: 给出n个单词,如果一个单词的尾和另一个单词的头字符相同,那么这两个单词可以相连。求这n个单词是否可以排成一列。应用欧拉图,构图:一个单词的头尾字幕分别作为顶点,每输入一个word,则该word的头指向word的尾有一条边。记录每个顶点的出入度。利用并查集先判断是否为SCC。如果是的话再判断奇数度节点为0个或者2个。


P3: 给出n个field以及m条边连接这m个field,要求遍历每条边2次。求这样的一条路径。
若该field组成的图是连通分量,那么由于每条边遍历2次,即等效的每个结点的度均为偶数。故必然存在欧拉回路。此时只需对原图作一次DFS,即可得到这样的路径。需要记录下每次访问的结点顺序,重复访问的也需打印出来。

P4: 给出一组单词,如果一个单词的尾和另一个单词的头字符相同,那么这两个单词可以相连。问这组单词能否排成一排,如果可以求出字典序最小的那个。构图:单词作为边,单词的首字符和尾字符作为结点。读入一个单词即添加一条边。用邻接链表法存边。首先判断图是否连通,如果是,再判断该图能否构成欧拉路径或者回路。如果是回路,从字符'a'开始寻找,找到第一个存在的字母,从这个字母遍历就行。如果是路径,那么必须从出度大于入度的那个结点开始访问。由于这个题目要求的是最小字典序,然后考虑我们建立邻接表时是采用头插法,那么我们将单词从小到大排序,由于我们遍历结点肯定是从小的结点开始的,这样遍历一个结点的邻接边的时候就会从大到小访问这个结点的边,无法满足字典最小。所以从大到小排序,遍历依node数组为准,每次遍历一条边设置下标表示已访问过。





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值