欧拉路问题—详解

先区分几个概念

欧拉路:从一个点S出发,不重不漏的经过每条边(允许重复经过一个点),最终去到另一个点T,的一条路径。
欧拉回路:从一个点S出发,不重不漏的经过每条边(允许重复经过一个点),最终回到这个点S,的一条路径。
欧拉图:存在欧拉回路的无向图。

这两种路的判定应该不用我讲吧?不会的请翻阅《金牌奥数(五年级)》

判定完后就是要求其方案了

欧拉路:这个好处理,起点终点都给了只要拼命找就好了。
好吧,也没那么简单。对于起点,先进行一次遍历,一路标记遍历过的节点,记下它遍历到第一个度数大于2的节点S',然后停止;终点也是如此,记节点为T'。第二次的遍历从S'开始,dfs直接暴力跑图即可,随便玩!反正终点一定在T'。再头补上S->S',末尾补上T'->T就找到了一种方案。
特别的,当起点和终点在一条线上时,起点会直接遍历到终点。这个时候判断一下,直接结束就好了。

欧拉回路:显然的一种求法是暴力dfs,因为从任意一个点出发最终都会回到这个点,没有任何限制。
可是随意的dfs起始点会导致设下一个断点(因为访问过的节点不能再访问),一旦有断点,就会使得我们的节点记录的不连续。比如这个图:

欧拉路问题—详解_第1张图片

从A->B->C->D->E->C->A是一条理想的一笔画路径,然而没头没脑的程序可能会跑出A->B->C->A ->D->E->C的路径。也不要紧,我们要做的只不过是把->D->E->C给插入到里面去。
神奇的人们想到一种做法,当把一个节点访问完后再将其放入答案队列。
这样为什么是对的呢?试想一下,如果在dfs一开头就将一个点x入队,那么这样的答案队列是以x向外扩张的。如果在回溯时入队,那么这样的答案队列将是内收的。再看看死胡同的问题,从一端跑出来,没问题;如果误入了死胡同,不要紧,我们的答案队列是内收的,所以记录的不是从x->S,而是S->x。来到x后,没有死胡同的世界随便玩!怎么跑都是一个欧拉回路!周游世界之后,再从x回到S,圆满收官。

神奇的回溯时入栈!巧妙的解决了这个问题。回想之前的入栈方法,一开始就入栈是深度优先遍历的基础,也可以用来求一种dfs序。开头和结尾都入栈在树上用的比较多,这是另一种dfs序,可以很方便的进行子树操作。结尾入栈,在无向图中无意间就成了欧拉回路(有的话),还有什么其它神秘的用处有待读者的发掘开采。

有一个问题,对于这个答案序列要倒序输出吗?我的想法是最好要。对于一个无向图而言是没有什么所谓的,因为abc跟cba没什么区别,而且都一定合法。而有向图则要倒序输出,因为一开始我们是按边的方向访问的,而方案是内收统计的,也就是说是反着的。养成习惯还是倒序输出吧~
在代码实现时有一个剪枝,每次访问完last[x]之后,last[x]这条边是不可能再被访问了,所以把last[x]=e[last[x]].next,不会影响答案,还能有效提升。
建议用手写栈代替dfs,详见代码。

例题

题目
poj2230 Watchcow

题解
正反方向各经过一次不就恰好是一条(x,y)和(y,x)吗?稍稍改一下模板就可以了。

代码

#include
#include
#include
using namespace std;
const int maxn=10010,maxm=50010;

int n,m;

struct E{int y,next;}e[maxm<<1];int len=1,last[maxn];
void ins(int x,int y)
{
    e[++len]=(E){y,last[x]};last[x]=len;
}

int ac=0,ans[maxm<<1];//debug maxn<<1
int top=0,sta[maxm<<1];
void euler()
{
    sta[++top]=1;//sta模拟dfs的栈,记录dfs中的每个点
    while(top)
    {
        int x=sta[top],k;
        if(k=last[x])
        {
            sta[++top]=e[k].y;
            last[x]=e[k].next;//剪枝,路径压缩
        }
        else
        {
            ans[++ac]=x;
            top--;
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    euler();
    for(int i=ac;i>=1;i--) printf("%d\n",ans[i]);//试过了,正序倒序没所谓
    return 0;
}

 

你可能感兴趣的:(递归/DFS,欧拉路,《算法竞赛进阶指南》刷书之旅)