欧拉路问题

euler路问题也称一笔画问题。

1.一张无向图,若存在一条从节点s到节点t的路径,恰好不重不漏地经过每条边一次(可以重复经过图中节点,最终回到节点s。

这条路径称该路径为s到t的euler回路。其实通过图中所有边的简单路就叫euler路。

2.特别的,如果存在一条从s出发的路径,恰好不重不漏地经过每条边一次最终回到s。

该条路径称为euler回路。其实就是闭合的欧拉路。

3.euler图:一张无向图,且无向图连通,每个点的度数都是偶数。其实就是包含euler回路的图。

4.euler路的判定:图中恰好有两个点度数为奇数,其他节点的度数为偶数。这两个度数为奇数的点就是起点与终点了。

5.euler回路的判定:图中所有点的度数都是偶数,任意点都是起点和终点。

dfs求出euler回路:

这道题保证是一个euler回路或者是euler路了,或者是euler回路加euler路,所以不需要再判断是否存在,直接求出字典序最小的答案即可。

首先是要看出起点应该在哪,发现如果有度数是奇数点的话起点就是最小的奇数点,而度数都是偶数点的话起点就在最小的偶数点。

数据范围小所以考虑直接邻接矩阵存储,然后字典序好得关键是答案输出的问题,回溯之后存答案,而不是回溯之前,为什么?

可以看出直接输出是不对的两个环套在一块,直接输出发现这个图连不起来了,所以回溯之后存。

究竟是什么原因:个人理解是由于如上图两环套在一起,一定要把第二个环完全嵌入第一个环之中,也就是说当你跑完第一个环时,第二个环还没跑,你要在跑完第一个环之前把第二个环给跑了才行。所以直接输出是不对滴~

#include<iostream>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<iomanip>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
#include<cstdio>
#include<map>
#include<deque>
#include<set>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void put(int x)
{
    if(x==0){putchar('0');putchar('\n');return;}
    if(x<0)x=-x,putchar('-');
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x=x/10;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int maxn=4002;
int n,m=0;
int a[maxn][maxn];
int b[maxn],len=0,minn=maxn,ru[maxn];
void dfs(int x)
{
    for(int i=1;i<=m;i++)
        if(a[i][x]!=0)
        {
            a[i][x]--;a[x][i]--;
            dfs(i);b[++len]=i;
        }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x,y;
        x=read();y=read();
        a[x][y]++;a[y][x]++;
        m=max(m,max(x,y));
        minn=min(minn,min(x,y));
        ru[x]++;ru[y]++;
    }
    //cout<<minn<<endl;
    for(int i=1;i<=m;i++)if(ru[i]%2==1){minn=i;break;}
    dfs(minn);
    b[++len]=minn;
    for(int i=len;i>=1;i--)put(b[i]);
    return 0;
}
View Code

复杂度是O(nm)的,其中n为点数,m为边数。复杂度有点高,况且当图很大是邻接矩阵存不下。

使用邻接表来存图,效率会更高。而且dfs递归乘数是m容易爆栈。再把dfs变成bfs形势的。

如果不要求字典序的话复杂度为O(n+m);

代码:

#include<iostream>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<iomanip>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
#include<cstdio>
#include<map>
#include<deque>
#include<set>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void put(int x)
{
    if(x==0){putchar('0');putchar('\n');return;}
    if(x<0)x=-x,putchar('-');
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x=x/10;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int maxn=4002;
int n,m=0;
int b[maxn],num=0,minn=maxn,ru[maxn];
int lin[maxn],ver[maxn],nex[maxn],len=1;
int q[maxn<<1],t=0,vis[maxn];
void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
void bfs()
{
    q[++t]=minn;
    while(t!=0)
    {
        int x=q[t],i=lin[x];
        while(i!=0&&vis[i]!=0)i=nex[i];//找到一条还未走过的边
        if(i!=0)//模拟了递归过程!
        {
            q[++t]=ver[i];//进队
            vis[i]=vis[i^1]=1;//成对变换len得为1
            lin[x]=nex[i];//删边
        }
        else t--,b[++num]=x;
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x,y;
        x=read();y=read();
        add(x,y);add(y,x);
        m=max(m,max(x,y));
        minn=min(minn,min(x,y));
        ru[x]++;ru[y]++;
    }
    for(int i=1;i<=m;i++)if(ru[i]%2==1){minn=i;break;}
    bfs();
    for(int i=num;i>=1;i--)put(b[i]);
    return 0;
}
View Code

针对这道题求字典序用邻接表就不太好用了,所以建议直接使用邻接矩阵。

下面是对欧拉路求出的题。使用上述方法不会超时。

这道题显然是欧拉路和欧拉回路的例题,数据范围很大所以考虑用栈模拟dfs的过程下面是代码。

#include<iostream>
#include<cmath>
#include<ctime>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<stack>
#include<algorithm>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<set>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void put(int x)
{
    if(x==0){putchar('0');putchar('\n');return;}
    if(x<0)x=-x,putchar('-');
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int maxn=50002;
int n,m;
int lin[maxn<<1],ver[maxn<<1],nex[maxn<<1],len=0;
int q[maxn<<2],t=0,b[maxn<<1],h=0;
void add(int x,int y)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
}
void bfs()
{
    q[++t]=1;
    while(t!=0)
    {
        int x=q[t],i=lin[x];
        if(i!=0)lin[x]=nex[i],q[++t]=ver[i];
        else t--,b[++h]=x;
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        int x,y;
        x=read();y=read();
        add(x,y);add(y,x);
    }
    bfs();
    for(int i=h;i>=1;i--)put(b[i]);
    return 0;
}
View Code

这里就会有疑问了对上述代码,会发现直接寻找下一条边,这样就可以满足当走完所有边时一定会回到起点么,这个答案肯定是肯定的。

灵光一闪。如1连2,2连3,3连1,然后1走向2,2走向1,1走向3,3走向1,然后发现出栈了,这不是没有满足条件吗?注意尽管这样想是没有满足条件的别忘了上面可是大环套小环的,也就是说接下来是3到2,2到3,答案的储存是倒序的也就是dfs之后的b数组应该是这样的1(走到1走不动了。3(走到3走不动了,2,3,1,2,1.。这样看似上面的走错了,其实也是得到了正确答案,大环套小环,把小环嵌套进答案。最后怎么保证终点是起点呢?显然不和1相连的点都是小环,然后他们一定是在1的前面就被输出了,而1走向某个点肯定还有某个点最终走向1,双向图嘛,所以由此可得最后的点一定是起点(也就是1。(本人对上述代码的理解和对题目的理解)。

就这样欧拉回路就被得到解决了,还要时常复习啊,然后就是概念得懂,最重要的就是dfs之后存答案实现嵌套作用!

穷且益坚。

posted @ 2018-12-10 21:09  chdy  阅读(841)  评论(0编辑  收藏  举报