图论 —— 图的遍历 —— 欧拉通路与欧拉回路问题
【基本概念】
- 欧拉通路:通过图中所有边一次且仅一次行遍所有顶点的通路
- 欧拉回路:通过图中所有边一次且仅一次行遍所有顶点的回路
- 欧拉图:具有欧拉回路的图
- 半欧拉图:具有欧拉通路而无欧拉回路的图
- 奇度点:与点相连的边的数目为奇数的点
- 偶度点:与点相连的边的数目为偶数的点
【欧拉通路/回路的判定】
1.无向图
1)欧拉通路:图是连通的,图中只有两个奇度点,分别是欧拉通路的两个端点
对于欧拉通路,除起点、终点外,每个点如果进入,显然一定要出去,因此都是偶点
2)欧拉回路:图是连通的,点均为偶度点
对于欧拉回路,每个点进入、出去的次数相等,因此没有奇点
2.有向图
1)欧拉通路:图是连通的,除两顶点外其余点的入度等于出度,且这两个顶点中,一个顶点入度比出度大1(起点),另一个入度比出度小1(终点)
2)欧拉回路:图是连通的,图中所有点的入度等于出度。
3.Fleury 算法
Fleury 算法是用于求无向图中欧拉回路的算法,其基本思想如下:
- 任取图 G 中一顶点 v0,令 P0=v0
- 假设沿 Pi=v0e1v1e2...eivi,走到顶点 vi,按下面的方法从 E(G)-{e1,e2,...,ei} 中选择 ei+1:
1)ei+1 与 vi 相关联
2)除非无别的边可供选择,否则 ei+1 不应是 Gi=G-{e1,e2,...,ei} 中的桥 - 当 2 不能再进行时,算法停止
可以证明的是,当算法停止时,所得到简单回路 Pm=v0e1v1e2...emvm(vm=v0) 为G中的欧拉回路
int n,m;
int start;//起点
int num;//奇度顶点个数
int degree[N];//顶点的度
int path[N];//存储欧拉回路
int cnt;//欧拉回路计数器
bool G[N][N];
stack<int> S;
void dfs(int x){
S.push(x);
for(int i=1;i<=n;i++){
if(G[x][i]){
G[x][i]=false;
G[i][x]=false;
dfs(i);
break;
}
}
}
void Fleury(int x){
S.push(x);
while(!S.empty()){
bool flag=false;
for(int i=1;i<=n;i++){
if(G[S.top()][i]==true){ //与起点有关联的边
flag=true;
break;
}
}
if(flag==true){ //如果有关联的边
int temp=S.top();
S.pop();
dfs(temp);
}
else{ //如果没有有关联的边
path[cnt++]=S.top();
S.pop();
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
G[x][y]=1;
G[y][x]=1;
degree[x]++;
degree[y]++;
}
cnt=1;
num=0;
start=1;
for(int i=1;i<=n;i++){
if(degree[i]&1){//如果某点的度是奇数
if(num==0)//记录起点
start=i;
num++;//奇度点个数+1
}
}
if(num==0||num==2){//如果存在奇度顶点,则从奇度顶点出发,否则从顶点0出发
Fleury(start);
for(int i=cnt-1;i>=1;i--)
printf("%d ",path[i]);
printf("\n");
}
else
printf("No Euler path.\n");
return 0;
}
4.并查集判断无向图中是否存在欧拉回路
当给出一个无向图时,若要求判断图中是否存在欧拉回路,可以使用并查集判断图是否连通,并统计每个节点的度数,依次来判断是否存在。
若图连通且所有点的度数为偶数,则说明该无向图中存在欧拉回路。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1001
using namespace std;
int n,m;
int degree[N];
int father[N];
int Find(int x){
if(father[x]==-1)
return x;
return father[x]=Find(father[x]);
}
void Union(int x,int y){
x=Find(x);
y=Find(y);
if(x!=y)
father[x]=y;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
memset(degree,0,sizeof(degree));
memset(father,-1,sizeof(father));
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
degree[x]++;
degree[y]++;
Union(x,y);
}
int cnt=0;//记录连通分量
for(int i=1;i<=n;i++)
if(Find(i)==i)
cnt++;
if(cnt!=1)//若cnt大于1,说明图不连通
printf("NO\n");
else{
int num=0;//统计度数为奇数的点
for(int i=1;i<=n;i++)
if(degree[i]&1)
num++;
if(num==0)
printf("YES\n");
else
printf("NO\n");
}
}
return 0;
}
【应用】
对于一般的单词首尾相连的问题,通常都是转化为有向图的欧拉通路问题。
例如:给出多个单词,问能不能将所有单词组成一个序列,序列的前一个单词的尾字母与后一个单词的头字母相同
如果把每个单词看出无向的边,那么最终求出的欧拉通路可能存在两个单词尾部和尾部相连的情况。
1.无向欧拉图打印欧拉通路/回路
输入保证一个有 n 个点,m 条边的具有欧拉回路或欧拉路径的无向图,要求打印出图的欧拉回路或通路。
如果要打印欧拉通路,输入的 start 一定要是起点之一,即:,否则只是乱序打印图中所有的边。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#define N 1001
using namespace std;
int n,m;
int G[N][N];
bool vis[N][N];//vis[i][j]=1表示点i到j之间存在一条边
void euler(int x){//打印以x为起点的欧拉回路或通路
for(int y=1;y<=n;y++){
if(vis[x][y]||vis[y][x]){
vis[x][y]=0;//去掉x-y这条边
vis[y][x]=0;//去掉y-x这条边
euler(y);
//首尾相连逆序打印欧拉通路
//printf("%d %d\n",x,y);
}
}
//逆序打印欧拉回路
printf("%d ",x);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
G[x][y]=1;
G[y][x]=1;
vis[x][y]=1;
vis[y][x]=1;
}
int start;
scanf("%d",&start);
euler(start);//打印以x为起点的欧拉回路或通路
return 0;
}
2.有向欧拉图打印欧拉通路/回路
输入保证是一 n 个点,m 条边的具有欧拉回路或欧拉路径的有向图,要求打印出图的欧拉回路或通路。
如果要打印欧拉通路,那么输入的 start 一定要是起点之一,即:,否则只是乱序打印图中所有的边。
1)邻接矩阵实现
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#define N 1001
using namespace std;
int n,m;
int G[N][N];
bool vis[N][N];//vis[i][j]=1表示点i到j之间存在一条边
void euler(int x){//打印以x为起点的欧拉回路或通路
for(int y=1;y<=n;y++){
if(vis[x][y]){
vis[x][y]=0;//去掉x-y这条边
euler(y);
//首尾相连逆序打印欧拉通路
//printf("%d %d\n",x,y);
}
}
//逆序打印欧拉回路
printf("%d ",x);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
G[x][y]=1;
vis[x][y]=1;
}
int start;
scanf("%d",&start);
euler(start);//打印以x为起点的欧拉回路或通路
return 0;
}
2)邻接链表实现
struct Edge{
int endd;
bool vis;
Edge(int endd,bool vis):endd(endd),vis(vis){}
};
int n,m;
vector<Edge> G[N];
void euler(int x) {
for(int i=G[x].size()-1; i>=0;i--) {
if (!G[x][i].vis){
G[x][i].vis = 1;
euler(G[x][i].endd);
}
}
printf("%d\n",x);
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(Edge(y,false));
G[y].push_back(Edge(x,false));
}
int start=1;
euler(start);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App