图论 —— 图的遍历 —— 欧拉通路与欧拉回路问题

【基本概念】

1.欧拉通路:通过图中所有边一次且仅一次行遍所有顶点的通路

2.欧拉回路:通过图中所有边一次且仅一次行遍所有顶点的回路

3.欧拉图:具有欧拉回路的图

4.半欧拉图:具有欧拉通路而无欧拉回路的图

5.奇度点:与点相连的边的数目为奇数的点

6.偶度点:与点相连的边的数目为偶数的点

【欧拉通路/回路的判定】

1.无向图

1)欧拉通路:图是连通的,图中只有两个奇度点,分别是欧拉通路的两个端点

对于欧拉通路,除起点、终点外,每个点如果进入,显然一定要出去,因此都是偶点

2)欧拉回路:图是连通的,点均为偶度点

对于欧拉回路,每个点进入、出去的次数相等,因此没有奇点

2.有向图

1)欧拉通路:图是连通的,除两顶点外其余点的入度等于出度,且这两个顶点中,一个顶点入度比出度大1(起点),另一个入度比出度小1(终点)

2)欧拉回路:图是连通的,图中所有点的入度等于出度。

3.Fleury 算法

Fleury 算法是用于求无向图中欧拉回路的算法,其基本思想如下:

  1. 任取图 G 中一顶点 v0,令 P0=v0
  2. 假设沿 Pi=v0e1v1e2…eivi,走到顶点 vi,按下面的方法从 E(G)-{e1,e2,…,ei} 中选择 ei+1:
    1)ei+1 与 vi 相关联
    2)除非无别的边可供选择,否则 ei+1 不应是 Gi=G-{e1,e2,…,ei} 中的桥
  3. 当 2 不能再进行时,算法停止

可以证明的是,当算法停止时,所得到简单回路 Pm=v0e1v1e2…emvm(vm=v0) 为G中的欧拉回路

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#define N 1001
using namespace std;
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;
}
/*
input:
9 14
1 2
1 8
2 3
2 8
2 9
3 4
4 5
4 6
4 9
5 6
6 7
6 9
7 8
8 9
output:
1 8 9 6 7 8 2 9 4 6 5 4 3 2 1
*/

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 一定要是起点之一,即:《图论 —— 图的遍历 —— 欧拉通路与欧拉回路问题》,否则只是乱序打印图中所有的边。

#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;
}
    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/u011815404/article/details/86590498
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞