欧拉回路

為什麼要写这篇博客,因为刚好在看离散数学的时候看到了欧拉回路,所以打算在OJ上面找一题练手,那么顺便也写一下这个知识点。

上次写字典序和全排列的那篇博客花费了不少的时间……我在考虑这篇要不也写得简短一点?但是这样又好像没有了效果。意识流吧。

——————正文——————

欧拉回路是说对于一个给定的图,你可以找到一条回路,使得其经过了任何一条边,且不会出现重复。

欧拉通路就是对于一个给定的图,找到一条通路,使得其经过了任何一条边,且不会出现重复。

用书本的话描述,就是:

欧拉回路:恰好包含图的每条边一次的回路

欧拉通路:恰好包含图的每条边一次的通路

两者的区别在于:欧拉回路的起点与终点是同一个点,但是欧拉通路就不一定了。

欧拉通路/欧拉回路 最常见的益智例子就是——一笔画


网上,手机上非常多这种一笔画的游戏,可能你会觉得一个能够顺利斩下各种智力游戏的人很牛B很酷,但是我觉得吧没什么比程序员敲几下键盘然后让电脑自动玩那个游戏更帅……就像这种就是程序员的杰作:


还有数独这类游戏也是,比较好玩的就是我之前些了一个解数独的程序(很多人应该写过),然后拿同学的手机来直接帮他通关……不好意思一直跑题,意思就是我们可以开始写一个专门玩“一笔画”这类游戏的程序了。当然写之前先要知道怎么找欧拉通路/欧拉回路。

————判断方法————

怎么判断一个图里面是否存在欧拉回路?好了直接抄书本的结论了:

结论1:在连通多重图里存在欧拉回路当且仅当每个顶点都有偶数度

同样地怎么样判断一个图里面是否存在欧拉通路?也直接抄棘轮了:

结论2:在连通多重图里存在欧拉通路当且仅当之多两个顶点有奇数度


结论1的具体证法可以查一下书,比较感性的证明就是:

必要性:首先从某一顶点a开始,出发时度减少1,每次经过任何顶点,顶点的度都减少2,而最终回到a时,度减少1,这样很容易知道欧拉回路每个顶点的度都是偶数,这样就证明了欧拉回路存在的必要性

充分性:但是是不是每个顶点的度都是偶数就一定存在欧拉回路呢,就是说“偶数度”这个条件是不是充分?接着我们对一个每个顶点都有偶数度的图G,构造一条欧拉回路。首先我们对G,从顶点a开始找一条尽可能长的回路,用e表示图的边,v表示图的顶点,那么这条回路可以这样表示—— a e1 v1 e2 v2 ... v(n-1) en a,现在这条回路我们称为A,这个时候,如果G中有任何一条边不在A中,那么就是说A不是一条欧拉回路,不过木有关系,我们这个时候一定能在 v1,v2...v(n-1),a 这n个顶点中,找到一个顶点 vx,其关联的边不在A中(为神马?因为这个图是连通的,如果A的顶点中所有关联边都在A中,那么A就是要找的欧拉回路)。对于vx,我们以其为起点,继续寻找新的回路B,要注意的是,之前已经走过的边不能再走了。这个时候,我们就要问——為什麼你一定能从这个点开始找到回路?因为每个顶点有偶数度,对于任何一个顶点,目前仍然剩下偶数个度(无论顶点是否在A中),又要问一个问题——在寻找回路B的时候不会出现死胡同吗?不会,从vx顶点开始,其所连接的剩下任何一个顶点都肯定具有偶数度,这些顶点所连接的顶点肯定也是有偶数度(除非连接的是vx),因此不会出现“没路走”的情况。好了,我们就这样又找到了回路B,我们还可以依照这样的做法,一直找回路C,回路D...直到没有任何顶点的边是不在回路A,B,C,D...里面的。找到这些回路之后,神奇的事情就发生了,我们可以拼接这些回路!而且一定是可以拼得起来的,如下图A和B:


然后拼接是这样的,将回路A从vx处打开,回路B也从vx处打开,然后接起来就可以了,如下图所示:


这样就是一条新的回路,為什麼可以这样拼接?这个问题就比较简单了,各位自己思考一下就可以知道了。

按照这样的拼接,最终所有的回路A,B,C,D之类的,全部可以拼成一条大回路,这样到最后我们就可以得到一条欧拉回路,充分性得证

这样结论1就感性地证明完了,欢呼一下吧,hooray~!


然后就是结论2了,这个的证明建议大家可以自己先想一下,大致也是通过先验证证明必要性,再构造证明充分性,练习一下结论1和结论2的区别(将两个奇数顶点之间连起来会怎么样?),就应该比较有思路了。这里就不写了。


————代码————

代码非常简短~但是吧,我只写了找欧拉回路的代码,欧拉通路的还没有写,迟点心血来潮再写吧。

在有向图里找欧拉回路的代码里面,有一点是非常巧妙的,就是你怎么样执行“拼接”这个过程?我曾经写了一个较为复杂的代码,提交到评测系统时总是出错,我也不知道為什麼,后来借鉴了网上的写法,代码长度瞬间缩短几倍……夸张吧。代码的思路是这样的,同样也是从某个点开始,使用递归的方式找路,如果那条边还没有经过,就走那条边,在递归的最后一层,当前的节点肯定已经是没有任何边是没有走过的,回到原来的点了,还记得经过节点时度的变化吗?因为到达这一层的时候如果发现这个节点没有出度,那么就是说回到起始点了,那么就可以返回到递归的上一层,在上一层里,以该层作为另外一个回路的起点,实行递归(有点感觉了没?),那么输出呢?我们可以在每一层的最后输出,那么输出的顺序就和走的顺序是相反的,各位可以自行调整过来(例如不输出而是使用压栈的方式)。大致代码如下,edge是邻接表,tmp是个临时的变量,index是当前这一层的节点编号。大家要练下手可以看一下这条题目(POJ 2230):点击打开链接


vector<int> edge[MAXN];
void euler(int index)
{
	for(int i=0;i<edge[index].size();i++)
	{
          tmp=edge[index][i];
	  if(tmp==-1)
	    continue;
          edge[index][i]=-1;
          euler(tmp);
	}
	printf("%d\n",index);
}

——————完——————

呼,累死了,如果文章有不妥的地方,欢迎指出。


******<转载说明>******

转载注明:诚实的偷包贼

原文地址:http://blog.csdn.net/fanfank/article/details/9048683

******<转载说明/>******


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页