zoukankan      html  css  js  c++  java
  • 【算法】欧拉路

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

    欧拉路

    定义与性质:

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

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

    大约是这样的:

    其中 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;
    }
    

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

  • 相关阅读:
    使用线程新建WPF窗体(公用进度条窗体)
    WPF--模板选择
    WPF命令(Command)介绍、命令和数据绑定集成应用
    WPF 员工卡条形码
    WPF入门教程系列(二) 深入剖析WPF Binding的使用方法
    R语言——实验5-聚类分析
    R语言——实验5-聚类分析
    Java学习---流与文件
    Java学习---流与文件
    Java学习---异常处理
  • 原文地址:https://www.cnblogs.com/EiffelA-blog/p/13833916.html
Copyright © 2011-2022 走看看