大家好,我是柏下微雨。
最近在学习欧拉路径的过程中,对于dfs遍历图有了更深的理解。
何为欧拉回路
七桥问题是:七座桥连接七个岛,怎么遍历所有桥使每座桥只经过一次,这就是欧拉路径问题。
欧拉回路则是在此基础上起点等于终点。
存在性
- 对于无向图,只存在零个或两个度数为奇数的点,该图存在欧拉路径。
只有零个奇点,存在欧拉回路。 - 对于有向图,只存在零个或两个出度和入度不相等的点,该图存在欧拉路径。
只有零个,存在欧拉回路。
说明
想要理解题解,我们首先要补充一个知识点:图的dfs后序遍历。
类似于树,图也可以做后序遍历,即先dfs邻接点再做输出。即dfs(e)语句在cout之前
vis[a]=true;
//cout<<a;
for (a 的邻接点 e)
if(!vis[e])
dfs(e);
cout<<a;
考虑图
起点为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)
题目:
解:
#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如果将遍历点改成遍历边,是否具有类似性质,欢迎大家补充。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。