歐拉路問題
歐拉路問題,俗稱“一筆畫”問題
給定一張無向圖。若存在一條從節點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炸了,沒測出來,但應該沒鍋
歐拉路問題