欧拉路径与欧拉回路

欧拉路径与欧拉回路

感觉这一块网上说的有点乱,很多东西都没有说清楚,或者都缺一些东西,所以在这里打算好好的总结与归纳一下关于欧拉路径与欧拉回路的问题。

概念

欧拉路径:从某一起点开始,可以沿某路径遍历图中每一条边一次且仅一次,则称此路径为欧拉路径

欧拉回路:若欧拉路径中的起点和终点相同,则其为欧拉回路

一般情况下,如果一个图是由欧拉回路构成的,我们则称之为欧拉图。否则,当其是由欧拉路径构成的话,我们就称之为半欧拉图。

充要条件关系:

欧拉回路:所有点的满足:入度 = 出度,那么沿任意一点为起点,一定存在方案形成欧拉回路。

欧拉路径:有仅有一个起点:入度 = 出度 - 1,有且仅有一个终点:出度 = 入度 - 1,。其他的点都满足:入度 = 出度。则我们从起点开始遍历即可。

细心的读者可能会发现,如果我们在欧拉路径上画一条由终点指向起点的路径的话,那么欧拉路径就会变成欧拉回路,所以我们可以看出他们事实上很容易相互转化的。

所以欧拉路径与欧拉回路除了在判断其充要条件时略有不用外,他们的求法是一样的,所以我们这里以欧拉路径为例。

无向图&有向图

Fleury + dfs

最简单也是最基础的做法。

这里采用邻接表存放图,在优化中大家就会发现这样有意想不到的好处。

@Frosero
#include <cstdio>
#include <cstring>
#include <stack>
#define MAXN 100010

using namespace std;

struct N{       //邻接矩阵存放图会有意想不到的好处
    int v,nex;
    bool vis;
};
N edge[MAXN];
int fir[MAXN],tot;

void add_edge(int u,int v){
    edge[tot].v = v;
    edge[tot].vis = false;
    edge[tot].nex = fir[u];
    fir[u] = tot++;
}

stack<int>stk;
int ans[MAXN],cnt;

void dfs(int s){        //Fluery寻找增广路
    stk.push(s);
    for(int i=fir[s];i!=-1;i=edge[i].nex)if(!edge[i].vis){
        edge[i].vis = true;
        dfs(edge[i].v);
        break;
    }
}

void Fluery(int s){      //Fluery主过程
    while(!stk.empty()) stk.pop(); stk.push(s);
    while(!stk.empty()){
        int u =  stk.top(); stk.pop();
        bool flag = false;
        for(int i=fir[u];i!=-1;i=edge[i].nex)if(!edge[i].vis){
            flag = true;    //已检测到存在增广路
            break;
        }
        if(flag) dfs(u);
        else ans[--cnt] = u;    //注意要逆序存放,才能正序输出
    }
}

int n,m;    //n为点的数量,从1开始计数,m为边的数量
int is[MAXN],os[MAXN];  //is代表入度,os代表出度

int main(){
    //freopen("in.in","r",stdin);
    //freopen("out.txt","w",stdout);
    while(scanf("%d %d",&n,&m)!=EOF){
        memset(fir,-1,sizeof(fir));
        memset(is,0,sizeof(is));
        memset(os,0,sizeof(os));
        tot = 0;
        int u,v;
        for(int i=0;i<m;i++){
            scanf("%d %d",&u,&v);
            add_edge(u,v);
            os[u]++; is[v]++;
        }
        int s = 1,t = 1,s_cnt = 0,t_cnt = 0;    //s为起点,t为终点
        bool flag = true;       //s_cnt为适合做起点的点数,t_cnt为适合做终点的点数
        for(int i=1;i<=n;i++){
            if(is[i] + 1 == os[i]){
                s = i;
                s_cnt++;
            }
            else if(is[i] == os[i] + 1){
                t = i;
                t_cnt++;
            }               //若一个点入度和出度相差2及以上,则不可能存在欧拉路径
            else if(is[i] != os[i]) flag = false;
        }                                               //欧拉路径存在的充要条件
        if(flag && ((s_cnt == 1 && t_cnt == 1) || (s_cnt + t_cnt == 0))){
            cnt = m + 1;    //m条边则一定且只经历m+1个点,否则也不是欧拉路径
            Fluery(s);
            if(cnt != 0) printf("No Eulers !\n");
            else{
                for(int i=0;i<m+1;i++) printf("%d ",ans[i]); printf("\n");
            }
        }
        else printf("No Eulers !\n");
    }

    return 0;
}

Fleury + bfs

然而上述算法适用性及其有限,其中一点就是:dfs过深导致爆栈

为解决此问题,我们可以把dfs过程用bfs来写。

代码如下:

@Frosero
#include <cstdio>
#include <cstring>
#include <stack>
#define MAXN 100010

using namespace std;

struct N{       //邻接矩阵存放图会有意想不到的好处
    int v,nex;
    bool vis;
};
N edge[MAXN];
int fir[MAXN],tot;

void add_edge(int u,int v){
    edge[tot].v = v;
    edge[tot].vis = false;
    edge[tot].nex = fir[u];
    fir[u] = tot++;
}

stack<int>stk;
int ans[MAXN],cnt;

void bfs(int s){        //Fluery寻找增广路,注意这里改成了bfs
    stk.push(s);
    while(true){
        bool flag = false;
        for(int i=fir[s];i!=-1;i=edge[i].nex)if(!edge[i].vis){
            edge[i].vis = true;
            flag = true;
            s = edge[i].v;
            stk.push(s);
            break;
        }
        if(!flag) break;
    }
}

void Fluery(int s){      //Fluery主过程
    while(!stk.empty()) stk.pop(); stk.push(s);
    while(!stk.empty()){
        int u =  stk.top(); stk.pop();
        bool flag = false;
        for(int i=fir[u];i!=-1;i=edge[i].nex)if(!edge[i].vis){
            flag = true;    //已检测到存在增广路
            break;
        }
        if(flag) bfs(u);
        else ans[--cnt] = u;    //注意要逆序存放,才能正序输出
    }
}

int n,m;
int is[MAXN],os[MAXN];  //is代表入度,os代表出度

int main(){
    //freopen("in.in","r",stdin);
    //freopen("out.txt","w",stdout);
    while(scanf("%d %d",&n,&m)!=EOF){
        memset(fir,-1,sizeof(fir));
        memset(is,0,sizeof(is));
        memset(os,0,sizeof(os));
        tot = 0;
        int u,v;
        for(int i=0;i<m;i++){
            scanf("%d %d",&u,&v);
            add_edge(u,v);
            os[u]++; is[v]++;
        }
        int s = 1,t = 1,s_cnt = 0,t_cnt = 0;    //s为起点,t为终点
        bool flag = true;       //s_cnt为适合做起点的点数,t_cnt为适合做终点的点数
        for(int i=1;i<=n;i++){
            if(is[i] + 1 == os[i]){
                s = i;
                s_cnt++;
            }
            else if(is[i] == os[i] + 1){
                t = i;
                t_cnt++;
            }               //若一个点入度和出度相差2及以上,则不可能存在欧拉路径
            else if(is[i] != os[i]) flag = false;
        }                                               //欧拉路径存在的充要条件
        if(flag && ((s_cnt == 1 && t_cnt == 1) || (s_cnt + t_cnt == 0))){
            cnt = m + 1;    //m条边则一定且只经历m+1个点,否则也不是欧拉路径
            Fluery(s);
            if(cnt != 0) printf("No Eulers !\n");
            else{
                for(int i=0;i<m+1;i++) printf("%d ",ans[i]); printf("\n");
            }
        }
        else printf("No Eulers !\n");
    }

    return 0;
}

优化

然而自习分析算法复杂度可得知,时间复杂度是为O(VE)的。

在Fluery的主过程中,我们在不断寻找是否存在增广路时重复搜索已标记vis = true 的边,因此我们只要利用邻接表的“删除边”的功能,就能把其复杂度降为O(E)。

代码如下:

@Frosero
#include <cstdio>
#include <cstring>
#include <stack>
#define MAXN 100010

using namespace std;

struct N{       //邻接矩阵存放图会有意想不到的好处
    int v,nex;  //注意这里不再使用vis来标记
};
N edge[MAXN];
int fir[MAXN],tot;

void add_edge(int u,int v){
    edge[tot].v = v;
    edge[tot].nex = fir[u];
    fir[u] = tot++;
}

stack<int>stk;
int ans[MAXN],cnt;

void bfs(int s){        //Fluery寻找增广路,注意我们改的还是这里  ^*_*^....嘿嘿
    stk.push(s);
    while(true){
        bool flag = false;
        int las = -2;
        for(int i=fir[s];i!=-1;i=edge[i].nex){
            flag = true;
            if(las == -2) fir[s] = edge[i].nex;
            else edge[s].nex = edge[i].nex;
            s = edge[i].v;
            stk.push(s);
            las = i;
            break;
        }
        if(!flag) break;
    }
}

void Fluery(int s){      //Fluery主过程
    while(!stk.empty()) stk.pop(); stk.push(s);
    while(!stk.empty()){
        int u =  stk.top(); stk.pop();
        bool flag = false;
        for(int i=fir[u];i!=-1;i=edge[i].nex){
            flag = true;    //已检测到存在增广路
            break;
        }
        if(flag) bfs(u);
        else ans[--cnt] = u;    //注意要逆序存放,才能正序输出
    }
}

int n,m;
int is[MAXN],os[MAXN];  //is代表入度,os代表出度

int main(){
    //freopen("in.in","r",stdin);
    //freopen("out.txt","w",stdout);
    while(scanf("%d %d",&n,&m)!=EOF){
        memset(fir,-1,sizeof(fir));
        memset(is,0,sizeof(is));
        memset(os,0,sizeof(os));
        tot = 0;
        int u,v;
        for(int i=0;i<m;i++){
            scanf("%d %d",&u,&v);
            add_edge(u,v);
            os[u]++; is[v]++;
        }
        int s = 1,t = 1,s_cnt = 0,t_cnt = 0;    //s为起点,t为终点
        bool flag = true;       //s_cnt为适合做起点的点数,t_cnt为适合做终点的点数
        for(int i=1;i<=n;i++){
            if(is[i] + 1 == os[i]){
                s = i;
                s_cnt++;
            }
            else if(is[i] == os[i] + 1){
                t = i;
                t_cnt++;
            }               //若一个点入度和出度相差2及以上,则不可能存在欧拉路径
            else if(is[i] != os[i]) flag = false;
        }                                               //欧拉路径存在的充要条件
        if(flag && ((s_cnt == 1 && t_cnt == 1) || (s_cnt + t_cnt == 0))){
            cnt = m + 1;    //m条边则一定且只经历m+1个点,否则也不是欧拉路径
            Fluery(s);
            if(cnt != 0) printf("No Eulers !\n");
            else{
                for(int i=0;i<m+1;i++) printf("%d ",ans[i]); printf("\n");
            }
        }
        else printf("No Eulers !\n");
    }

    return 0;
}

到此,欧拉路径及欧拉回路知识基础就学好了,可以进军更复杂的混合图等啦~~

时间: 2024-12-28 13:19:58

欧拉路径与欧拉回路的相关文章

欧拉路径和欧拉回路

转载自:http://www.cnblogs.com/zhourongqing/archive/2012/09/18/2690088.html 欧拉路径和欧拉回路欧拉路径:从某结点出发一笔画成所经过的路线叫做欧拉路径.欧拉回路:在欧拉路径的基础上又回到起点.a.凡是由偶点组成的连通图,一定可以一笔画成.画时可以把任一偶点为起点,最后一定能以这个点为 终点画完此图. b.凡是只有两个奇点的连通图(其余都为偶点),一定可以一笔画成.画时必须把一个奇点为起点,另 一个奇点终点. c.其他情况的图都不能

10.5欧拉路径和欧拉回路(Euler Paths and Circuits)

10.5欧拉路径和欧拉回路(Euler Paths and Circuits) 引入:七桥问题 "一笔画"-->针对边而言 欧拉图(Eulerian graph) 图G的欧拉回路(Euler circuit)指的是遍历G中每一条边的简单回路(simple circuit), 这样的轨迹称为欧拉环游(Euler tour) 图G的欧拉路径(Euler path)指的是遍历G中每一条边的简单路径(simple path), 这样的轨迹称为欧拉轨迹(Euler trail) 有欧拉回路

杭电ACM1116——Play on Words~~欧拉路径与欧拉回路

这一题,相比之前做的题目,增加了欧拉路径的求解.而且这一题是有向图.题目大概的意思就是成语接龙,能接起来就算可以打开门,因此要考虑两种,一种是回路,另外一种是一条路径. 第一次WR就是因为没有考虑回路这一个因素. 有向图中,欧拉回路与欧拉路径的求解方法: 1.欧拉回路: 首先当然是图连通,其次就是所以顶点的入度都等于出度. 2.欧拉路径: 首要的还是图连通,然后就是存在一个顶点的出度大入度1,存在一个顶点的入度大出度1,其他顶点的入度等于出度. 这些就不再证明.图的连通可以用DFS来判断或者并查

欧拉除了函数,还有个回路----图论之路之欧拉路径欧拉回路

首先我们来百度一下,欧拉路径以及回路的定义: 若图G中存在这样一条路径,使得它恰通过G中每条边一次,则称该路径为欧拉路径.若该路径是一个圈,则称为欧拉(Euler)回路. 具有欧拉回路的图称为欧拉图(简称E图).具有欧拉路径但不具有欧拉回路的图称为半欧拉图. 通俗来说,就是欧拉路径就是图中的每条边经过却只经过一次的路径,而最后回到起点的路径就是欧拉回路. 那给你一个图怎么判断存不存在,欧拉路径或者欧拉回路呢 首先,判断图是不是连通的,这个就很简单了,dfs或者并查集都可以. 然后就是根据定理 欧

nyist 42 一笔画 (欧拉回路 + 并查集)

nyoj42 分析: 若图G中存在这样一条路径,使得它恰通过G中每条边一次,则称该路径为欧拉路径. 若该路径是一个圈,则称为欧拉(Euler)回路. 具有欧拉回路的图称为欧拉图(简称E图).具有欧拉路径但不具有欧拉回路的图称为半欧拉图. 先说一下欧拉路径.欧拉回路的充要条件: 1.无向连通图G是欧拉图,当且仅当G不含奇数度结点(G的所有结点度数为偶数): 2.无向连通图G含有欧拉通路,当且仅当G有零个或两个奇数度的结点: 3.有向连通图D是欧拉图,当且仅当该图为连通图且D中每个结点的入度=出度

poj2337 欧拉路径

poj2337 这道题昨天晚上开始做,今天才A.但是问题想透了, 发现其实没那么难 题目大意: 给你一些单词,如果一个单词的末尾字符与另一个单词首字符相同,则两个的单词可以连接.问是否可以把所有单词连接起来,并且每个单词只能用一次. 分析: 可以把每个单词看成是一条边,单词的首尾字符看做是两个相连的点.我们可以把它看成有向图的欧拉路径问题(欧拉路径,欧拉回路不太明白的自己百度吧). 一个有向图含有欧拉通路,首先图是连通的,并且当且仅当该图所有顶点的入度 =出度, 或者起始顶点入度 = 出度 -

poj 1386 欧拉路径

1 /* 2 题意:给出N个单词,一个单词的头字母和另一个单词的尾字母相同则可以相连,问这N个单词是否能完全相连成一行 3 4 题解:求欧拉路径 5 首先以每个单词的首字母和尾字母为点并且连边,然后用DFS求该图是否连通,然后根据点的入度和出度判断是否存在 6 欧拉路径或者欧拉回路(存在回路也是符合要求的) 7 */ 8 #include <cstdio> 9 #include <cstring> 10 11 int map[30][30]; 12 int in[30],out[3

欧拉路

欧拉路径和欧拉回路欧拉路径:从某结点出发一笔画成所经过的路线叫做欧拉路径.欧拉回路:在欧拉路径的基础上又回到起点.a.凡是由偶点组成的连通图,一定可以一笔画成.画时可以把任一偶点为起点,最后一定能以这个点为 终点画完此图. b.凡是只有两个奇点的连通图(其余都为偶点),一定可以一笔画成.画时必须把一个奇点为起点,另 一个奇点终点. c.其他情况的图都不能一笔画出.(有偶数个奇点除以2便可算出此图需几笔画成.) 欧拉回路和欧拉路径的判断欧拉回路:无向图:每个顶点的度数都是偶数,则存在欧拉回路.有向

nyoj42 一笔画问题

题目链接 分析: 若图G中存在这样一条路径,使得它恰通过G中每条边一次,则称该路径为欧拉路径.若该路径是一个圈,则称为欧拉(Euler)回路. 具有欧拉回路的图称为欧拉图(简称E图).具有欧拉路径但不具有欧拉回路的图称为半欧拉图. 先说一下欧拉路径.欧拉回路的充要条件: 1.无向连通图G是欧拉图,当且仅当G不含奇数度结点(G的所有结点度数为偶数): 2.无向连通图G含有欧拉通路,当且仅当G有零个或两个奇数度的结点: 3.有向连通图D是欧拉图,当且仅当该图为连通图且D中每个结点的入度=出度 4.有