6.4.4 欧拉回路
有一条名为Pregel的河流经过Konigsberg城,城中有7座桥,把河中的两个岛与河岸连接起来,当地居民热衷于一个难题,是否存在一条路线,可以不重复地走遍7座桥
首先是抽象为平常中我们常见的一笔画问题,这样的路线称为欧拉道路(eulerian path)
点击查看欧拉回路
C..................
. . .
. . .
A.................D
. . .
. . .
B..................
不难发现,在欧拉道路中,“进”和“出”是对应的--除了起点和终点外,其它点的“进出”次数应该相等,换句话说,除了起点和终点外,其他点的度数(degree)应该是偶数
很可惜在七桥问题中,所有四个点的度数均是奇数(这样的点也称为奇点),因此不可能存在欧拉道路,上述条件也是一个充分条件--如果一个无向图是联通的,且最多只有两个奇点,则一定存在欧拉道路。如果有两个奇点,则必须从其中一个奇点出发,另一个奇点终止;如果奇点不存在,则可以从任意点出发,最终一定会回到该点(即起点和终点重合,这个可以通过反证法来证明的),此时这样特殊的道路叫做欧拉回路
用类似推理方式可以得到有向图的结论:最多只能有两个点的入度不等于出度,而且必须是其中一个点的出度恰好比入度大1(把它作为起点),另一个的入度比出度大1(把它作为终点)
当然还有一个前提条件,在忽略边的方向后,图必须是联通的,否则与欧拉道路的题设不服(无向图老问题了,此时退化成有向图)
下面是程序,它同时适用于欧拉道路和欧拉回路(终点和起点是同一点,或者说不存在奇点),但是如果需要打印的是欧拉道路,在主程序中调用的时候,参数必须是道路的起点,另外,打印的顺序是逆序的,因此在真正使用这份代码时,应当把printf语句替换成一条push语句,把边(u,v)压入一个栈内
点击查看代码
void euler(int u) {
for(int v = 0; v < n; v++) if(G[u][v] && !vis[u][v]) {
vis[u][v] = vis[v][u] = 1;//这边应该是标记该边是否已经走过
euler(v);//进行递归
printf("%d %d\n", u, v);
}
}
尽管上面的代码只使用于无向图,但不难改成有向图,把vis[u][v]=vis[v][u]=1改成vis[u][v]=1就可以了,从无向退化成有向了
根据连通性和度数可以判断出无向图和有向图是否存在欧拉道路和欧拉回路
可以用DFS构造欧拉回路(这边的思路参考上节中的拓扑排序中的dfs,两者的构造有非常类似的地方,只不过欧拉回路允许存在有向环罢了)
注意做图的题目的时候时常需要有抽象思维进行图像化
Play_On_Words题解
切记open的过去式没有双写,不要习惯性多打字母opened
注意题目的意思是可能输入重复的单词,而不是单词可以重复使用,否则本题的难度将会上升一个难度,笔者原先将该问题想复杂了
本题就是简单的欧拉道路的一个有向图判断,注意有向图的两个问题,第一,无向图形式必须是联通的,第二,奇点最多有两个,同时出度和入度的区别
点击查看笔者代码
#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 30;
int in[maxn], out[maxn], temp[maxn][maxn], sign[maxn];//G[u][v] u to v u out v in
void dfs(int val) {
sign[val] = 0;
for(int i = 0; i < 26; i++)
if(temp[val][i] && sign[i]) {
dfs(i);
}
}
int main() {
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int t;
cin >> t;
while(t--) {
memset(in, 0, sizeof(in));
memset(out, 0, sizeof(out));
memset(temp, 0, sizeof(temp));
memset(sign, 0, sizeof(sign));
int n;
cin >> n;
while(n--) {
string line;
cin >> line;
out[line[0]-'a']++;
in[line[line.length()-1]-'a']++;
temp[line[0]-'a'][line[line.length()-1]-'a'] = 1;
temp[line[line.length()-1]-'a'][line[0]-'a'] = 1;
sign[line[0]-'a'] = 1;
sign[line[line.length()-1]-'a'] = 1;
}
int cnt = 0;
for(int i = 0; i < 26; i++) {
if(in[i] > out[i]+1 || in[i]+1 < out[i]) {
cnt = 3;
break;
}
if(in[i] == out[i]+1 || in[i]+1 == out[i]) cnt++;
if(cnt > 2) break;
}
int sum = 0;
for(int i = 0; i < 26; i++) {
if(sign[i]) {
dfs(i);
break;
}
}
for(int i = 0; i < 26; i++) if(sign[i]) sum++;
if(sum) cnt = 3;
if(cnt <= 2) cout << "Ordering is possible." << endl;
else cout << "The door cannot be opened." << endl;//注意有向图的欧拉道路成立的条件之一是该有向图的无向图形式是联通的
}
return 0;
}
/*//读懂题目的意思,不是单词可以重复使用,而是可能存在同样的单词
for(int i = 0; i < 26; i++) {
if(in[i] != out[i]){
int flag = 1;
if(in[i] > out[i]) {
for(int j = i; j < 26; j++) {
if(G[i][j]) {
in[j] += in[i]-out[i];
flag = 0;
break;
}
}
}else{
for(int j = i; j < 26; j++) {
if(G[j][i]) {
out[j] += out[i]-in[i];
flag = 0;
break;
}
}
}
if(flag) {
if(in[i] == out[i]+1 || out[i] == in[i]+1) cnt++;
else{
cnt = 3;
break;
}
}
}
if(cnt > 2) break;
}
*/
笔者的代码中偷懒了,并没有进行出度入度不同的判断,这点读者可以加上去使得代码更加完整,只能说欧拉yyds,欧拉道路的理解就到此为止了
总的来说就是对一笔画问题的一个判断,在有向图中,具体化成了出度入度,在无向图中就只剩下了度数,以及奇点的概念
那么这边作者的分析是,把字母看作结点,单词看作有向边,则问题有解当且仅当图中有欧拉路径。前面讲过有向图存在欧拉路径的条件是底图(忽略边方向得到的无向图连通),且度数满足上面讨论过的条件
判断连通的方法有两种,一是之前介绍过的dfs,而是第11章介绍的并查集,可以根据自己的喜好来选择,呜呜,第十一章。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)