大家好,我是柏下微雨。
最近在学习欧拉路径的过程中,对于dfs遍历图有了更深的理解。

何为欧拉回路

七桥问题是:七座桥连接七个岛,怎么遍历所有桥使每座桥只经过一次,这就是欧拉路径问题。
欧拉回路则是在此基础上起点等于终点。

存在性

  • 对于无向图,只存在零个或两个度数为奇数的点,该图存在欧拉路径。
    只有零个奇点,存在欧拉回路。
  • 对于有向图,只存在零个或两个出度和入度不相等的点,该图存在欧拉路径。
    只有零个,存在欧拉回路。

说明

想要理解题解,我们首先要补充一个知识点:图的dfs后序遍历。
类似于树,图也可以做后序遍历,即先dfs邻接点再做输出。即dfs(e)语句在cout之前

vis[a]=true;
//cout<<a;
for (a 的邻接点 e)
    if(!vis[e])
        dfs(e);
cout<<a;

考虑图
image.png
起点为A。
对于先序遍历,输出结果可能为ABCD或ACDB(取决于先遍历B还是C)
先遍历B的路径图为:

dfs(a)
\cout<<a;
\dfs(b)
     \cout<<b;
\dfs(c)
     \cout<<c;
     \dfs(d)
         \cout<<d;//输出abcd

对于后序遍历,输出结果可能为DCBA或BDCA(取决于先遍历B还是C)
先遍历B的路径图为:

dfs(a)
\dfs(b)
    \dfs(b)
    \cout<<b;
\dfs(c)
    \dfs(d)
        \cout<<d;
    \cout<<c;
\cout<<a;//输出bdca    

可见先序遍历和后序遍历的输出并不是正好相反的。

到目前为止,由于遍历的点不能再遍历,和树的dfs其实没有本质区别。对于每个环,由于vis数组的存在,环没有意义,图就退化成树。
但是,我们改变vis数组的意义,令其表示遍历过的边。即

//cout<<a;
for (a 的邻接点 e)
    if(!vis[a][e]){
        vis[a][e]=true;
        dfs(e);
    }
cout<<a;

于是前序遍历的路径图变为:(先遍历B)

dfs(a)
\cout<<a;
\dfs(b)
    \cout<<b;
\dfs(c)
    \cout<<c;
    \dfs(d)
        \cout<<d;
        \dfs(a);
            \cout<<a;//输出abcda

后序遍历的路径图:(先遍历C)

dfs(a)
\dfs(c)
    \dfs(d)
        \dfs(a)
            \dfs(b)
                \cout<<b
            \cout<<a;
        \cout<<d;
    \cout<<c;
\cout<<a;//输出badca

细心的小伙伴可能发现先遍历C和遍历B的树形不一样,其关键在于对于每个环,回到起点的时候是否还有边可以遍历,如果没有,就和开始讲的一样。如果有就会增大深度。

但是这个输出还不能满足欧拉回路的要求,如果将输出点改为输出边:

for (a 的邻接点 e)
    if(!vis[a][e]){
        vis[a][e]=true;
        //cout<<id[a][e];
        dfs(e);
        cout<<id[a][e];    
    }

路径图省略。
经过分析可以得知:对于这样后序遍历,有一种性质,无法构成环的那一个分支,永远第一个被输出,因为对于可以构成环的分支,回到起点后,总能找到继续dfs的路径,直到走到无法构成环的分支。而走到无法构成环的那一个分支后,由于无法继续dfs,便只能开始输出。因此输出的反序即欧拉路径。

解码(来源于acwing)

题目:image.png
解:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010, M = 400010;

int type;
int n, m;
int h[N], e[M], ne[M], idx;
bool used[M];
int ans[M], cnt;
int din[N], dout[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u)
{
    for (int &i = h[u]; ~i;)
    {
        if (used[i])
        {
            i = ne[i];
            continue;
        }

        used[i] = true;
        if (type == 1) used[i ^ 1] = true;

        int t;

        if (type == 1)
        {
            t = i / 2 + 1;
            if (i & 1) t = -t;
        }
        else t = i + 1;

        int j = e[i];
        i = ne[i];
        dfs(j);

        ans[ ++ cnt] = t;
    }
}

int main()
{
    scanf("%d", &type);
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);

    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        if (type == 1) add(b, a);
        din[b] ++ , dout[a] ++ ;
    }

    if (type == 1)
    {
        for (int i = 1; i <= n; i ++ )
            if (din[i] + dout[i] & 1)
            {
                puts("NO");
                return 0;
            }
    }
    else
    {
        for (int i = 1; i <= n; i ++ )
            if (din[i] != dout[i])
            {
                puts("NO");
                return 0;
            }
    }

    for (int i = 1; i <= n; i ++ )
        if (h[i] != -1)
        {
            dfs(i);
            break;
        }

    if (cnt < m)
    {
        puts("NO");
        return 0;
    }

    puts("YES");
    for (int i = cnt; i; i -- ) printf("%d ", ans[i]);
    puts("");

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/159788/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

其他

我想到,bfs如果将遍历点改成遍历边,是否具有类似性质,欢迎大家补充。


柏下微雨
1 声望0 粉丝