高考考完开始进行算法竞赛的康复训练(好吧其实是从零开始直接学,过完高三什么都忘了T T),在POJ上做了一些水题。今天做到这道欧拉回路,很有感触,因为第一次学这个算法的时候并没有学透,今天再做才发现原来求欧拉回路的算法有这么精妙。

先讲题意吧,原文链接:http://poj.org/problem?id=2230。大意是给你一个无向图,要求找一条路径,走过每一条边恰好两次,且每次走的方向不同。很容易就能想到把这图转化为有向图求欧拉回路。题目保证一定能找到从1点出发回到1点的答案。

现在的关键就是求这个回路了,一个朴素的想法就是直接DFS,每次经过一条边就删除它,回退的时候再还原。如果经过了原图边数恰好两倍就找到了一个答案。但这个算法实在太慢,直接TLE。

现在我们考虑搜索的过程:如果不还原一个点所遍历的边,那么当这个点去掉所有边之后,再去掉这个点,剩下的图是什么?先上图,这是题目所给的样例的草图:
还没遍历时的图像
然后从1开始,我们丧心病狂一点,直接把1就搞掉不碰与1无关的边,反正遍历的顺序是不确定的,这么做未尝不可。那么就变成了这样:
删除1和相关的边的情况
我们看到,剩下的还是一个欧拉回路。我们可以猜想,一个欧拉回路,删掉一个点,仍然是一个欧拉回路。这个结论其实非常容易证明,随便想想就明白这是对的。进一步研究,会发现一个更普遍的结论:从一个欧拉回路中拖走一个小欧拉回路,结果也是一个欧拉回路。这个性质也很明显,而且前一个结论是后一个结论的推论。由这个结论,我们可以考虑,有公共点的两个欧拉回路其实是可以拼接的。于是下面这个搜索算法就很好理解了:

void DFS(int now)
{
    for (int p=G.Head[now];p;p=G.Pre[p])
    {
        if (!G.Vis[p])
        {
            G.Vis[p]=1;
            DFS(G.V[p]);
        }
    }
    printf("%d\n",now);
}

G是图,我用了一个邻接表,G.Vis是标记这条边是否走过。如果一条边还没走过,就标记然后走下去,关键在如果一个点已经走完了怎么办:直接输出。每次从v出发回到v,就是扒走了一个回路,根据栈的性质,这样得到的顺序其实是相反的。不过由于这题的图中边是成对出现,所以没关系,倒过来也是可以的。输出的过程就是把一个个欧拉回路拼在一起。对于无向图,通过拼接也可以得到欧拉回路,不过相对于这个算法要复杂不少。最后提供Gitcafe代码链接,如有需要参考请看:https://gitcafe.com/linmx0130/OJCode/blob/master/POJ/P2230/main.cpp


Michael_Lin
338 声望17 粉丝

OIer/ACMer,C/C++/Java/Js/Php/python业余开发者