歐拉圖 歐拉回路 歐拉通路 Euler


歐拉圖

本文鏈接: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 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM