1. 程式人生 > >歐拉路問題

歐拉路問題

一個 ret con splay 滿足 中間 pre .net 離開

歐拉路問題,俗稱“一筆畫”問題
給定一張無向圖。若存在一條從節點S到節點T的路徑,恰好不漏不重的經過每一條邊一次(可以重復經過節點),則稱該路徑為S到T的歐拉路

若存在一條從節點S出發,恰好不漏不重地經過每一條邊(可以重復經過圖中節點)最終回到起點S,則該路徑稱為歐拉回路。
存在歐拉回路的無向圖稱為歐拉圖

歐拉圖判定
一張無向圖為歐拉圖,當且僅當無向圖聯通,且每個節點的度都是偶數

歐拉路存在性判定
一張無向圖存在歐拉路,當且僅當無向圖連通,並且圖中恰好有兩個節點的度數為奇數,其他節點的度數為偶數。這兩個度數為奇數的節點就是起點S和終點T

歐拉回路的方案

在保證一張無向圖時歐拉圖時
歐拉圖每個節點度數為偶數說明:只要到達一個節點,就必定存在有一條尚未走過的邊可以離開該點。


故在偽代碼中調用dfs(1),不斷遞歸,每次都走到“從x出發的第一條未訪問的邊”的另一端點y,最終一定能回到節點1,產生一條回路
但是這條回路不能保證經過圖中的每條邊。所以dfs函數會繼續考慮從x出發的其他未訪問的邊,找到第二條回路

偽代碼實際找出了若幹條回路,我們需要把這些回路按照適當的方法拼接在一起,形成整張圖的歐拉回路,一個拼接方法就是把第二條回路嵌入第一條回路中間

而偽代碼中的棧,替我們完成了這個拼接工作,最後,把棧中的所有節點倒序輸出,就得到了一條歐拉回路

上述算法的復雜度時O(NM)。因為一個點會被重復遍歷多次,每次都會掃描與它相連的所有的邊,雖然大部分的邊已經訪問過了
假設我們使用鄰接表來存儲無向圖,我們可以在訪問一條邊(x, y)後,及時修改表頭head[x],令它指向下一條邊。


這樣我們每次只需去除head[x],就自然跳過了所有已經訪問過的邊
因為歐拉回路的DFS的遞歸層數時O(M)級別,容易造成系統棧溢出。我們可以用另一個棧,模擬機器的遞歸過程把代碼轉為非遞歸實現
最後復雜度為O(N + M)

技術分享圖片
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 500010;
 4 struct shiki {
 5     int y, net;
 6 }e[maxn << 1];
 7 int lin[maxn], len = 0;
 8 int s[maxn], ans[maxn];
9 bool vis[maxn]; 10 int n, m, t_f = 0, t_s = 0; 11 12 inline int read() { 13 int x = 0, y = 1; 14 char ch = getchar(); 15 while(!isdigit(ch)) { 16 if(ch == -) y = -1; 17 ch = getchar(); 18 } 19 while(isdigit(ch)) { 20 x = (x << 1) + (x << 3) + ch - 0; 21 ch = getchar(); 22 } 23 return x * y; 24 } 25 26 inline void insert(int xx, int yy) { 27 e[++len].y = yy; 28 e[len].net = lin[xx]; 29 lin[xx] = len; 30 } 31 32 inline void euler() { 33 s[++t_f] = 1; 34 while(t_f > 0) { 35 int x = s[t_f], i = lin[x]; 36 while(i && vis[i]) i = e[i].net; 37 if(i) { 38 s[++t_f] = e[i].y; 39 vis[i] = vis[i ^ 1] = true; 40 lin[x] = e[i].net; 41 } 42 else t_f--, ans[++t_s] = x; 43 } 44 } 45 46 int main() { 47 n = read(), m = read(); 48 len = 1; 49 for(register int i = 1; i <= m; ++i) { 50 int x = read(), y = read(); 51 insert(x, y); 52 insert(y, x); 53 } 54 euler(); 55 cout << "One of all Euler circuits is : "<< ; 56 for(register int i = t_s; i >= 1; --i) 57 cout << ans[i] << ; 58 return 0; 59 }
歐拉回路方案

例題:Watchcow(poj2230)

給定N個點M條邊的無向圖(1 <= N <= 10^4, 1 <= M <= 5 * 10^4),求一條路徑,從節點1出發,最後回到節點1,並且滿足每條邊恰好被沿著正,反兩個方向分別經過一次。
若有多種方案,輸出一種即可

按照一般的存儲方式,無向邊會在鄰接表中以正、反兩個方向分別被保存一次,若沒有vis標記,則按照表頭數組head的更新方法,每條無向邊會被正反各經過一次,恰好符合題目要求

技術分享圖片
 1 #include<iostream>
 2 #include<iomanip>
 3 #include<cstdio>
 4 #include<ctime>
 5 #include<cstdlib>
 6 #include<algorithm>
 7 #include<cstring>
 8 #include<stack>
 9 #include<queue>
10 #include<map>
11 #include<vector>
12 #include<cmath>
13 using namespace std;
14 const int maxn = 50010;
15 struct shiki {
16     int y, net;
17 }e[maxn << 1];
18 int lin[maxn], len = 0;
19 int n, m, t_f = 0, t_s = 0;
20 int s[maxn], ans[maxn];
21 
22 inline int read() {
23     int x = 0, y = 1;
24     char ch = getchar();
25     while(!isdigit(ch)) {
26         if(ch == -) y = -1;
27         ch = getchar();
28     }
29     while(isdigit(ch)) {
30         x = (x << 1) + (x << 3) + ch - 0;
31         ch = getchar();
32     }
33     return x * y;
34 }
35 
36 inline void insert(int xx, int yy) {
37     e[++len].y = yy;
38     e[len].net = lin[xx];
39     lin[xx] = len;
40 }
41 
42 inline void euler() {
43     s[++t_f] = 1;
44     while(t_f > 0) {
45         int x = s[t_f], i = lin[x];
46     //    while(i) i = lin[i];
47         if(i) {
48             s[++t_f] = e[i].y;
49             lin[x] = e[i].net;
50         }
51         else t_f--, ans[++t_s] = x;
52      }
53 }
54 
55 int main() {
56 //    freopen("watchcow.in", "r", stdin);
57 //    freopen("watchcow.out", "w", stdout); 
58     n = read(), m = read();
59     len = 1;
60     for(register int i = 1; i <= m; ++i) {
61         int x = read(), y = read();
62         insert(x, y);
63         insert(y, x);
64     }
65     euler();
66     for(register int i = t_s; i >= 1; i--)
67         cout << ans[i] << \n;
68     return 0;
69 }
poj炸了,沒測出來,但應該沒鍋

歐拉路問題