欧拉回路
欧拉图: 就是从任意一个点开始都可以一笔画完整个图
半欧拉图: 必须从某个点开始才能一笔画完整个图。
对于无向图 , 是欧拉图当且仅当 是连通的且没有奇度顶点。
对于无向图 , 是半欧拉图当且仅当 是连通的且 中恰有 个或 个奇度顶点。
对于有向图 , 是欧拉图当且仅当 的所有顶点属于同一个强连通分量且每个顶点的入度和出度相同。
对于有向图 , 是半欧拉图当且仅当
如果将有向图中的所有有向边退化为无向边时,那么所有顶点属于同一个连通分量。
最多只有一个顶点的出度与入度差为 。
最多只有一个顶点的入度与出度差为 。
所有其他顶点的入度和出度相同。
Fleury
一个偏暴力的算法,应用范围更广,但是复杂度高,暂时用不到,以后再学。
Hierholzer(逐步插入回路法)
先记录每个点的度,判断是否存在欧拉回路或者路
从一个指定起点出发,不断的走与其相邻的边,走过的边就删掉,回溯时碰到出入度为0的点就将其加入答案队列,显然答案是逆序加入的,所以要从队尾开始输出。
该算法,有回路给出回路,没回路给出路,都没有则结束
参考他的手推递归树方便理解
以一道例题给出模板
P2731 [USACO3.3]骑马修栅栏 Riding the Fences
/*
Hierholzer
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1025;
multiset<int> to[maxn]; // 要使输出的路径尽可能小,利用multiset的排序
int degree[maxn];
int road[maxn], cnt; // 记录路径及路径长度
void dfs(int x) {
for(auto a = to[x].begin(); a != to[x].end(); a=to[x].begin()) { // auto C++11标准,可以自动匹配数据类型
int u = *a;
to[x].erase(a);
to[u].erase(to[u].find(x)); // 删边
dfs(u);
}
road[cnt++] = x; // 往队列里插答案
}
int main() {
int m, a, b;
scanf("%d", &m);
for(int i = 0; i < m; i++) {
scanf("%d%d", &a, &b);
degree[a]++, degree[b]++; // 记录点的度
to[a].insert(b); // 建边
to[b].insert(a); // 双向边
}
int s = -1, e = -1; // 起点与终点
for(int i = 1; i <= 1024; i++) {// 判断每个点的度数
if(degree[i]&1) { // 奇度顶点
if(s == -1) s = i; // 一个作为起点
else if(e == -1) e = i; // 一个作为终点
else exit(1); // 如果奇度顶点 >= 3 没有欧拉路
// 退出整个程序,终止进程,并返回1给操作系统。
// 由于返回0代表程序正常退出,返回1等其他数字通常代表异常终止。可通过返回的具体数值判断出错源。
}
}
if(s == -1) s = 1; // 全是偶度顶点则直接从 开始答案最小
dfs(s);
for(int i = cnt-1; i >= 0; i--) { // 倒叙输出为答案
printf("%d\n", road[i]);
}
return 0;
}