欧拉路和欧拉回路

欧拉环:图中经过每条边一次且仅一次的环;
欧拉路径:图中经过每条边一次且仅一次的路径;
欧拉图:有至少一个欧拉环的图;
半欧拉图:没有欧拉环,但有至少一条欧拉路径的图。

【无向图】
一个无向图是欧拉图当且仅当该图是连通的(注意,不考虑图中度为0的点,因为它们的存在对于图中是否存在欧拉环、欧拉路径没有影响)且所有点的度数都是偶数;一个无向图是半欧拉图当且仅当该图是连通的且有且只有2个点的度数是奇数(此时这两个点只能作为欧拉路径的起点和终点);

证明:因为任意一个点,欧拉环(或欧拉路径)从它这里进去多少次就要出来多少次,故(进去的次数+出来的次数)为偶数,又因为(进去的次数+出来的次数)=该点的度数(根据定义),所以该点的度数为偶数。

【有向图】
一个有向图是欧拉图当且仅当该图的基图(将所有有向边变为无向边后形成的无向图,这里同样不考虑度数为0的点)是连通的且所有点的入度等于出度;一个有向图是半欧拉图当且仅当该图的基图是连通的且有且只有一个点的入度比出度少1(作为欧拉路径的起点),有且只有一个点的入度比出度多1(作为终点),其余点的入度等于出度。

证明:与无向图证明类似,一个点进去多少次就要出来多少次。

【无向图、有向图中欧拉环的求法】
与二分图匹配算法类似,是一个深度优先遍历的过程,时间复杂度O(M)(因为一条边最多被访问一次)。核心代码(边是用边表存储的而不是邻接链表,因为无向图中需要对其逆向的边进行处理,在有向图中,可以用邻接链表存储边):

[cpp]  view plain copy
  1. void dfs(int x)  
  2. {  
  3.     int y;  
  4.     for (int p=hd[x]; p != -1; p=ed[p].next) if (!ed[p].vst) {  
  5.         y = ed[p].b;  
  6.         ed[p].vst = 1;  
  7.         ed[p ^ 1].vst = 1;     //如果是有向图则不要这句  
  8.         dfs(y);  
  9.         res[v--] = y + 1;  
  10.     }  
  11. }  


可以参考http://blog.csdn.net/logic_nut/article/details/4474307

传说中的套圈法。

算法思想的朴素表达

对于欧拉图,从一个节点出发,随便往下走(走过之后需要标记一下,下次就不要来了),必然也在这个节点终止(因为除了起始节点,其他节点的度数都是偶数,只要能进去就能出来)。这样就构成了一个圈,但因为是随便走的,所以可能会有些边还没走过就回来了。我们就从终止节点逆着往前查找,直到找到第一个分叉路口,然后从这个节点出发继续上面的步骤,肯定也是可以找到一条回到这个点的路径的,这时我们把两个圈连在一起。当你把所有的圈都找出来后,整个欧拉回路的寻找就完成了。

寻找欧拉回路时,起始节点是可以任意选择的。如果是有基度顶点要寻找欧拉通路,则从基度顶点出发就好了,上述步骤依然有效。

算法思想的书面表达

一个解决此类问题基本的想法是从某个节点开始,然后查出一个从这个点出发回到这个点的环路径。现在,环已经建立,这种方法保证每个点都被遍历.如果有某个 点的边没有被遍历就让这个点为起点,这条边为起始边,把它和当前的环衔接上。这样直至所有的边都被遍历。这样,整个图就被连接到一起了。 

更正式的说,要找出欧拉路径,就要循环地找出出发点。按以下步骤: 

任取一个起点,开始下面的步骤 
如果该点没有相连的点,就将该点加进路径中然后返回。 
如果该点有相连的点,就列一张相连点的表然后遍历它们直到该点没有相连的点。(遍历一个点,删除一个点) 
处理当前的点,删除和这个点相连的边, 在它相邻的点上重复上面的步骤,把当前这个点加入路径中. 
下面是伪代码: 

# circuit is a global array 
find_euler_circuit 
circuitpos = 0 
find_circuit(node 1) 

# nextnode and visited is a local array 
# the path will be found in reverse order 
find_circuit(node i) 

if node i has no neighbors then 
circuit(circuitpos) = node i 
circuitpos = circuitpos + 1 
else 
while (node i has neighbors) 
pick a random neighbor node j of node i 
delete_edges (node j, node i) 
find_circuit (node j) 
circuit(circuitpos) = node i 
circuitpos = circuitpos + 1 
要找欧拉路径, 只要简单的找出一个度为奇数的节点,然后调用 find_circuit 就可以了.

一点点代码(pku 2337里面截过来的,具体请看http://blog.csdn.net/logic_nut/archive/2009/08/22/4473174.aspx)

[cpp]  view plain copy
  1. //主函数中调用下面这个函数  
  2. euler(start, -1); //因为直接从start出发,所以第二个参数用-1代替,输出的时候要忽略掉。  
  3. //euler函数就是用的传说中的套圈法,是一个迭代的过程  
  4. //npath是一个全局变量,记录已经找到的边的数量  
  5. //边存储在adj[]中,是用数组实现的链表结构(就跟很多hash那样,首节点的数据域不用,只有指针部分有效)  
  6. void euler(int cur, int edgeN) //cur当前到达的节点 edgeN上一被选择的边,即上一个节点通过edgeN到达的cur  
  7. {   
  8.     int i;  
  9.     while(adj[cur].nxt != -1)  
  10.     {  
  11.         i=adj[cur].nxt;  
  12.         adj[cur].nxt=adj[i].nxt;//相当于是删除掉使用了的边  
  13.         euler(adj[i].end,i);  
  14.     }  
  15.     path[npath++] = edgeN; //后序记录,如果要保持搜索时候边的优先级,则逆向输出  
  16. }  


要注意的是在res中写入是逆序的,所以初始的v应设成(边数-1)。
但是有一个问题是,这是递归实现的,当点数过多时有爆栈危险,所以最好使用非递归:

[cpp]  view plain copy
  1. void dfs()  
  2. {  
  3.     int x = 0, y, tp = 1; stk[0] = 0;  
  4.     re(i, n) now[i] = hd[i];  
  5.     bool ff;  
  6.     while (tp) {  
  7.         ff = 0;  
  8.         for (int p=now[x]; p != -1; p=ed[p].next) if (!ed[p].vst) {  
  9.             y = ed[p].b;  
  10.             ed[p].vst = 1;  
  11.             ed[p ^ 1].vst = 1;     //如果是有向图则不要这句  
  12.             now[x] = p; stk[tp++] = y; x = y; ff = 1; break;  
  13.         }  
  14.         if (!ff) {  
  15.             res[v--] = x + 1;  
  16.             x = stk[--tp - 1];  
  17.         }  
  18.     }  
  19. }  



中国邮递员问题问题(管梅谷,1960):一位邮递员从邮局出发投递邮件,经过他所管辖的每条街道至少一次,然后回到邮局。请为他选择一条路线,使其所行路程尽可能短。图论模型:求赋权连通图中含所有边的权最小的闭途径。这样的闭途径称为最优邮路。

思路:(1)若G是Euler图,则G的Euler闭迹便是最优邮路,可用Fleury算法求得;(2)若G不是Euler图,则含有所有边的闭途径必须重复经过一些边,最优邮路要求重复经过的边的权之和达到最小。闭途径重复经过一些边,实质上可看成给图G添加了一些重复边(其权与原边的权相等),最终消除了奇度顶点形成一个Euler图。因此,在这种情况下求最优邮路可分为两步进行:首先给图G添加一些重复边得到一个Euler图G*,使得添加边的权之和最小;然后用Fleury算法求G*的一条Euler闭迹。这样便得到G的最优邮路。问题是:如何给图G添加重复边得到Euler图G*,使得添加边的权之和最小?


Edmonds-Johnson方法的基本思想:先求出G中所有2k个奇度顶点间的最短路。为了从中选出k条权最小且无公共端点的最短路,以G的所有奇度点为顶点构造赋权完全图K2k,两顶点连边上的权为这两个奇度点在G中最短路的权。求赋权完全图K2k的最小权完美匹配,该完美匹配的k条边对应的k对顶点间的最短路必定无公共边(否则,若两条路有公共边,则删去公共边后可得到两条连接奇度顶点的更短路,从而对应出权更小的完美匹配,矛盾)。沿这k条最短路添加重复边,便可得到最优邮路Euler图G*。然后找出G*的Euler闭迹,即得到最优邮路。

Edmonds-Johnson算法:Step1. 若G中无奇度顶点,令G*=G,转step2;否则转step3。Step2. 求G*中的Euler闭迹,结束。Step3. 求G中所有奇度顶点对之间的最短路。Step4. 以G中奇度顶点集为顶点集,构作赋权完全图K2k,其中各边的权为对应两顶点在G中最短路的权。Step5. 求K2k中最小权完美匹配M。Step6. M中每条边所匹配的两点在G中都对应有一条最短路,在G中沿这些最短路添加重复边,得Euler图G*,转step2。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值