【算法】欧拉路

学长(姐?)昨晚讲了欧拉路,写个博加深一下印象

欧拉路

定义与性质:

欧拉路是一种路(滑稽)。

一种不重不漏的一笔画走完所有路径,且每一条路都至少走一遍,也只能走一遍。

大约是这样的:

其中 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 来找出整个欧拉路。

  1. 先统计每个点的度数;

  2. 再判断有几个点的度数为奇数:

    • 如果有两个点或者没有点的度数为奇数,随便找个点作为起点开始 dfs;
    • 如果有多个点的度数为奇数,输出 No answer
  3. dfs 过程中用栈来维护欧拉路的路径;

  4. 最后输出路径。

完整代码 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;
}

不知道以前有没有人这么干的

原文地址:https://www.cnblogs.com/EiffelA-blog/p/13833916.html