【算法】欧拉路 欧拉路
学长(姐?)昨晚讲了欧拉路,写个博加深一下印象
定义与性质:
欧拉路是一种路(滑稽)。
一种不重不漏的一笔画走完所有路径,且每一条路都至少走一遍,也只能走一遍。
大约是这样的:
其中 1 -> 2 -> 3 -> 5 -> 10 -> 3 -> 4 -> 8 -> 9 -> 11 -> 8 -> 7 -> 6
就是一条欧拉路。
当然 1 -> 2 -> 3 -> 10 -> 5 -> 3 -> 4 -> 8 -> 11 -> 9 -> 8 -> 7 -> 6
也是一条欧拉路。
然后 6 -> 7 -> 8 -> 11 -> 9 -> 8 -> 4 -> 3 -> 10 -> 5 -> 3 -> 2 -> 1
所以反过来还是欧拉路。
由此可以看出,一个图的欧拉路并不是唯一的。
而欧拉回路就是起点与终点相同的欧拉路,可以在欧拉路的起点与终点之间连一条边构成欧拉回路:
代码实现:
思路:
由欧拉路的性质可知,如果一个图(无向图)为欧拉路,那么这个图的所有点只有两个点的度数为奇数,或者没有点的度数为奇数,
其他所有点的度数为偶数。
而欧拉路的起点或终点就是那两个度数为奇数的点。(欧拉回路随便找个点当起点就行)
所以可以通过一个 dfs 来找出整个欧拉路。
先统计每个点的度数;
再判断有几个点的度数为奇数:
- 如果有两个点或者没有点的度数为奇数,随便找个点作为起点开始 dfs;
- 如果有多个点的度数为奇数,输出
No answer
;dfs 过程中用栈来维护欧拉路的路径;
最后输出路径。
完整代码 1(用邻接矩阵来存图):
#include <iostream>
#include <cstdio>
#define MAXN 1050
#define F1(i, a, b) for (int i = a; i <= b; ++i)
#define F2(i, a, b) for (int i = a; i >= b; --i)
using namespace std;
int f[MAXN][MAXN]; // f[i][j] 存储从 i 到 j 之间有几条边
int du[MAXN], sta[MAXN]; // du[i] 存储 i 点的度;sta 数组为栈用来记录欧拉路的路径
int top = 0, n, m;
void dfs(int t) {
F1(i, 1, n) { // 挨个寻找与 t 连接的边
while (f[t][i] || f[i][t]) { // 如果这里有边,while 防止多边连接两点相同
--f[t][i]; // 该边数量减一,代表该边走过了
--f[i][t]; // 无向图
dfs(i);
}
}
sta[++top] = t; // 回溯时把 t 点放进栈里
}
int main() {
int x, y, k = 1, cnt = 0;
scanf("%d %d", &n, &m);
F1(i, 1, m) {
scanf("%d %d", &x, &y);
++f[x][y]; // 存边
++f[y][x];
++du[x]; // 统计度数
++du[y];
}
F2(i, 1, n) if (du[i] % 2) ++cnt;
if (cnt == 2 || cnt == 0) dfs(k);
else {
printf("No answer
");
return 0;
}
F2(i, top, 1) printf("%d
", sta[i]);
return 0;
}
完整代码 2(链式前向星存图):
前置:
毫无疑问链式前向星存图一定是比邻接矩阵存图更优的,特别是在稀疏图中
但很多人不用链式前向星是因为欧拉路大部分是无向图,
标记一条道路被走过需要对这条道路的反向道路也标记为走过,
而这一点不太好实现,所以很多人直接用邻接矩阵来存图。
而这个时候就需要对链式前向星的理解力了:
链式前向星存的是边,同时用 head 数组来记录上一条边的序号,而遍历的时候用 head 数组和 nxt 来遍历,
存图用 add 函数来实现。
现在再回到欧拉路上:
欧拉路通常为无向图,如果用链式前向星来存,那么就会用 add 正着存一遍,再倒着存一遍,所以两条边的序号是连着的,
如果序号为奇数(我是从一开始存),那么这条边就是正着存的,那它的下一条边就是这条边的反边;
如果序号为偶数,那么这条边就是倒着存的,那它的上一条边就是这条边的正边。
代码:
#include <iostream>
#include <cstdio>
#define MAXN 1001
#define F1(i, a, b) for (int i = a; i <= b; ++i)
#define F2(i, a, b) for (int i = a; i >= b; --i)
using namespace std;
int js = 0, n, m, top = 0;
int head[MAXN], du[MAXN], sta[MAXN];
struct edge {
int v, nxt;
bool book; // book 记录该边是否被走过
}e[MAXN << 1];
inline int read() { // 读入优化
int sto = 0, fg = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') fg = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
sto = (sto << 1) + (sto << 3) + (ch ^ 48);
ch = getchar();
}
return sto * fg;
}
void add(int u, int v) { // 存边函数
e[++js].v = v;
e[js].nxt = head[u];
head[u] = js;
}
void dfs(int t) {
for (int i = head[t]; i; i = e[i].nxt) {
if (!e[i].book) { // 用链式前向星遍历
if (i % 2) e[i + 1].book = 1;
else e[i - 1].book = 1; // 将反向边标记
e[i].book = 1; // 正向边标记(好像没用)
dfs(e[i].v);
}
}
sta[++top] = t; // 存路
}
int main() {
int x, y, k = 1, cnt = 0;
n = read(); m = read();
F1(i, 1, m) {
x = read(); y = read();
add(x, y); // 存正边
add(y, x); // 存反边
++du[x]; ++du[y];
}
F1(i, 1, n) if (du[i] % 2) ++cnt, k = i;
if (cnt == 2 || cnt == 0) dfs(k);
else {
printf("No answer
");
return 0;
}
F2(i, top, 1) printf("%d ", sta[i]);
return 0;
}
不知道以前有没有人这么干的