欧拉通路与欧拉回路问题

倖福魔咒の 提交于 2020-01-30 03:56:49

版权声明:本文为CSDN博主「Alex_McAvoy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011815404/article/details/86590498

【基本概念】

  1. 欧拉通路:通过图中所有边一次且仅一次行遍所有顶点的通路
  2. 欧拉回路:通过图中所有边一次且仅一次行遍所有顶点的回路
  3. 欧拉图:具有欧拉回路的图
  4. 半欧拉图:具有欧拉通路而无欧拉回路的图
  5. 奇度点:与点相连的边的数目为奇数的点
  6. 偶度点:与点相连的边的数目为偶数的点

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

1.无向图

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

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

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

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

2.有向图

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

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

3.并查集判断无向图中是否存在欧拉回路

当给出一个无向图时,若要求判断图中是否存在欧拉回路,可以使用并查集判断图是否连通,并统计每个节点的度数,依次来判断是否存在。

若图连通且所有点的度数为偶数,则说明该无向图中存在欧拉回路。

#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;
}

4.Fleury 算法

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

1、任取图 GG 中一顶点 v0v_0,令 P0=v0P_0=v_0

2、假设沿 Pi=v0e1v1e2...eiviP_i=v_0e_1v_1e_2...e_iv_i,走到顶点 viv_i,按下面的方法从 E(G){e1,e2,...,ei}E(G)-\left\{e_1,e_2,...,e_i\right\} 中选择 ei+1e_i+1:

  1. ei+1e_i+1viv_i 相关联
  2. 除非无别的边可供选择,否则 ei+1e_i+1 不应是 Gi=G{e1,e2,...,ei}G_i=G-\left\{e_1,e_2,...,e_i\right\} 中的桥

3、当 2 不能再进行时,算法停止

可以证明的是,当算法停止时,所得到简单回路 Pm=v0e1v1e2...emvm(vm=v0)P_m=v_0e_1v_1e_2...e_mv_m(v_m=v_0)GG 中的欧拉回路。

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

【应用】

对于一般的单词首尾相连的问题,通常都是转化为有向图的欧拉通路问题。

例如:给出多个单词,问能不能将所有单词组成一个序列,序列的前一个单词的尾字母与后一个单词的头字母相同

如果把每个单词看出无向的边,那么最终求出的欧拉通路可能存在两个单词尾部和尾部相连的情况。

1.无向欧拉图打印欧拉通路/回路

输入保证一个有 nn 个点,mm 条边的具有欧拉回路或欧拉路径的无向图,要求打印出图的欧拉回路或通路。

如果要打印欧拉通路,输入的 startstart 一定要是起点之一,即:deg(start)=1deg(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[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.有向欧拉图打印欧拉通路/回路

输入保证是 nn 个点,mm 条边的具有欧拉回路或欧拉路径的有向图,要求打印出图的欧拉回路或通路。

如果要打印欧拉通路,那么输入的 startstart 一定要是起点之一,即:deg+(start)deg(start)=1|deg^+(start)-deg^-(start)|=1,否则只是乱序打印图中所有的边。

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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!