歐拉圖
本文鏈接:http://www.cnblogs.com/Ash-ly/p/5397702.html
定義:
歐拉回路:圖G的一個回路,如果恰通過圖G的每一條邊,則該回路稱為歐拉回路,具有歐拉回路的圖稱為歐拉圖。歐拉圖就是從圖上的一點出發,經過所有邊且只能經過一次,最終回到起點的路徑。
歐拉通路:即可以不回到起點,但是必須經過每一條邊,且只能一次。也叫"一筆畫"問題。
性質:
歐拉回路:一個歐拉回路,刪掉一個點,仍然是一個歐拉回路。從一個歐拉回路拖走一個小歐拉回路,結果也是一個歐拉回路。
判定(充要):
歐拉回路:1: 圖G是連通的,不能有孤立點存在。
2: 對於無向圖來說度數為奇數的點個數為0;對於有向圖來說每個點的入度必須等於出度。
歐拉通路:1: 圖G是連通的,無孤立點存在。
2: 對於無向圖來說,度數為奇數的的點可以有2個或者0個,並且這兩個奇點其中一個為起點另外一個為終點。對於有向圖來說,可以存在兩個點,其入度不等於出度,其中一個出度比入度大1,為路徑的起點;另外一個入度比出度大1,為路徑的終點。
算法(求歐拉回路):
Fleury算法:
設圖G是一個無向歐拉圖,則按照下面算法求歐拉回路:
1:任取G中一個頂點v0,令P0 = v0.
2:假設沿Pi = v0e1v1e2v2……eivi 走到了頂點 vi,按照下面方法從E(i) = E(G) - {e1, e2, e3,…,ei} 中選e(i + 1),選擇后刪除e(i +1)這條邊.
a):e(i+1)余vi關聯
b):除非無別的邊可選,否則e(i+1)不應是Gi = G – {e1,e2,…,ei} 中的橋.假若迫不得已選的是橋,除刪除這條邊之外,還應該再把孤立點從Gi中移除(選擇橋邊必然會形成孤立的點).
3:當步驟 2 無法繼續執行時停止算法.
當算法停止時,所得到的簡單回路 Pm = = v0e1v1e2v2e3v3……emvm (vm = v0) 為圖G的一條歐拉回路.
下面用圖來描述:
隨便選擇一個起點 v1。當前處在 v1 點,有兩種走法 v1 – v9,v1 – v10,這倆條邊都不是橋邊,那么隨便選擇一個,<v1, v10>這條邊吧。那么圖就會成為這樣.Eu = (走過的邊集){<v1, v10>}
當前到了 V10 點,有<v10,v4>,<v10,v3>,<v10, v8>,先看<v10,v8>這條邊吧,如果選擇了這條邊那么圖就會成為這樣:
很顯然形成了兩個圖,上下兩個圖不連通,即<v10, v8>這條邊就是所謂的橋邊,算法中說除非別無他選,否則不應該選擇橋邊,那么這條邊就不能選擇。回到上面,由於<v10,v4>,<v10,v3>都不是橋邊,所以隨便選擇<v10,v4>吧. Eu={<v1, v10>,<v10,v4>}
到了 v4 這個點,<v4, v2>這條邊是橋邊,但是別無選擇,只好選擇這條邊.選擇完這條邊這時不僅要從原圖中刪除這條邊,由於點4成為了孤點,所以這個點也該從原圖刪除。Eu={<v1,v10>,<v10,v4>,<v4,v2>}.
同理到達 v2 只好選擇<v2,v3>,刪除孤點 v2和邊. Eu{<v1,v10>,<v10,v4>,<v4,v2><v2,v3>}.
別無他選,<v3,v10>。Eu{<v1,v10>,<v10,v4>,<v4,v2><v2,v3><v3,v10>}.
同樣,選擇<v10, v8>,Eu{<v1,v10>,<v10,v4>,<v4,v2><v2,v3><v3,v10>,<v10,v8>}.
此時到了 v8 同第一次到達v10時的情況,不能選擇<v8,v9>這條橋邊,選擇<v8,v6>,Eu{<v1,v10>,<v10,v4>,<v4,v2><v2,v3><v3,v10>,<v10,v8>,<v8,v6>}.
到達v6,選擇<v6,v7>,刪點刪邊,Eu{<v1,v10>,<v10,v4>,<v4,v2><v2,v3>,<v3,v10>,<v10,v8>,<v8,v6>,<v6,v7>}.以下就不給圖了(逃;
然后接下來的選擇都是別無他選,依次選擇<v7,v8><v8,v9><v9,v1>,最后得到的歐拉邊集Eu{<v1,v10>,<v10,v4>,<v4,v2><v2,v3>,<v3,v10>,<v10,v8>,<v8,v6>,<v6,v7>,<v7,v8><v8,v9><v9,v1>},於是我們就得到了一條歐拉回路.
代碼:
個人感覺時間復雜度不如基本法,主要是判斷橋邊的時間復雜度有點高,達到O(1)才和基本法一樣,所以就放棄寫了。
基本(套圈)法
首先從一個節點(v0)出發,隨便往下走(走過的邊需要標記一下,下次就別走了),當走到不能再走的時候,所停止的點必然也是起點(因為所有的點的度數都是偶數,能進去肯定還會出來,再者中間有可能再次經過起點,但是如果起點還能繼續走,那么就要繼續往下搜索,直到再次回來時不能往下搜索為止),然后停止時,走過的路徑形成了一個圈,但因為是隨便走的,所以可能有些邊還沒走就回來了,那些剩下的邊肯定也會形成一個或者多個環,然后可以從剛才終止的節點往前回溯,找到第一個可以向其他方向搜索的節點(vi),然后再以這個點繼續往下搜索,同理還會繼續回到該點(vi),於是這個環加上上次那個環就構成了一個更大的環,即可以想象成形成了一條從 v0 到 vi的路徑,再由 vi 走了一個環回到 vi,然后到達v0 的一條更長的路徑,如果當前的路徑還不是最長的,那么繼續按照上面的方法擴展。只需要在回溯時記錄下每次回溯的邊,最后形成的邊的序列就是一條歐拉回路。如果要記錄點的順序的話,那么每訪問一個點,就把這個點壓入棧中,當某個點不能繼續搜索時,即在標記不能走的邊是,這個點成為了某種意義上的孤點,然后把這個點輸出最后得到的就是一條歐拉回路路徑的點的軌跡。
總之,求歐拉回路的方法是,使用深度優先搜索,如果某條邊被搜索到,則標記這條邊為已選擇,並且即使回溯也不能將當前邊的狀態改回未選擇,每次回溯時,記錄回溯路徑。深度優先搜索結束后,記錄的路徑就是歐拉回路。
下面用圖描述一遍:
假設我們選擇從v1開始走,由於隨便走,所以可能出現以下走法
第一步:v1 -- v9
第二步:v9 -- v8
第三步:v8 -- v10
第四步:v10 -- v1
此時由於走過的邊不能再走,那么從 v1 就無法繼續向下探索,所以往前回溯,記錄邊集Eu{<v1, v10>},此時回溯到 v10 ,發現可以繼續走,那么
第五步: v10 -- v3
第六步: v3 -- v2
第七步: v2 -- v4
第八步: v4 – v10
發現已經無路可走,那么繼續回溯,記錄回溯路徑得到Eu{<v1,v10>, <v10, v4>, <v4, v2>, <v2, v3>, <v3, v10>, <v10, v8>},此時回溯到了 v8.發現可以向其他方向搜索, 那么
第九步:v8 -- v6
第十步:v6 --v7
第十一步:v7-- v8
又無路可走,繼續回溯Eu{<v1,v10>, <v10, v4>, <v4, v2>, <v2, v3>, <v3, v10>, <v10, v8>, <v8, v7>, <v7, v6>,<v6,v8>,<v8,v9>,<v9,v1>},到這里整個DFS就結束了,我們得到的邊集Eu就是一條歐拉回路。
具體實現與分析:
使用鏈式前向星和DFS實現尋找歐拉回路的算法,用鏈式前向星存無向邊時每條邊要存儲兩次。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <stack> 5 #include <queue> 6 using namespace std; 7 8 const int MAXV = 100 + 7; 9 const int MAXE = 100 * 100 + 7; 10 int head[MAXV]; 11 int V, E; 12 13 typedef struct EdgeNode 14 { 15 int to; 16 int w; 17 int next; 18 }edgeNode; 19 edgeNode Edges[MAXE]; 20 21 bool visit[2 * MAXE]; 22 stack<int> stv; 23 queue<int> quv;//點集 24 queue<int> que;//邊集 25 26 void EulerDFS(int now) 27 { 28 stv.push(now);//每訪問一個點,就把該點壓入棧 29 for(int k = head[now]; k != -1; k = Edges[k].next) 30 { 31 if(!visit[k]) 32 { 33 visit[k] = true; //有向圖每條邊保存了兩次,也要標記兩次 34 if(k & 1) 35 visit[k + 1] = true; 36 else 37 visit[k - 1] = true; 38 EulerDFS(Edges[k].to); 39 que.push(k);//回溯時記錄邊 40 } 41 } 42 quv.push(stv.top());//記錄點 43 stv.pop(); 44 } 45 46 int main() 47 { 48 //freopen("in.txt", "r", stdin); 49 scanf("%d%d", &V, &E); 50 memset(head, -1, sizeof(head)); 51 for(int i = 1; i <= E; i++) 52 { 53 int u, v, w; 54 scanf("%d%d%d", &u, &v, &w); 55 Edges[2 * i - 1].to = v; //雙向儲存邊 56 Edges[2 * i - 1].w = w; 57 Edges[2 * i - 1].next = head[u]; 58 head[u] = 2 * i - 1; 59 Edges[2 * i].to = u; 60 Edges[2 * i].w = w; 61 Edges[2 * i].next = head[v]; 62 head[v] = 2 * i; 63 } 64 memset(visit, false, sizeof(visit)); 65 EulerDFS(1); 66 return 0; 67 }