hiho歐拉路·二 --------- Fleury算法求歐拉路徑


 

hiho歐拉路·二

 

分析:

小Ho:這種簡單的謎題就交給我吧!

小Hi:真的沒問題么?

<10分鍾過去>

小Ho:啊啊啊啊啊!搞不定啊!!!骨牌數量一多就亂了。

小Hi:哎,我就知道你會遇到問題。

小Ho:小Hi快來幫幫我!

小Hi:好了,好了。讓我們一起來解決這個問題。

<小Hi思考了一下>

小Hi:原來是這樣。。。小Ho你仔細觀察這個例子:

技術分享

因為相連的兩個數字總是相同的,不妨我們只寫一次,那么這個例子可以寫成:3-2-4-3-5-1。6個數字剛好有5個間隙,每個間隙兩邊的數字由恰好對應了一塊骨牌。

如果我們將每一個數字看作一個點,每一塊骨牌看作一條邊。你覺得是怎么樣的呢?

小Ho:以這個例子來說的話,就是:

技術分享

要把所有的骨牌連起來,也就是把所有的邊都走一次。咦,這不是歐拉路問題么!

小Hi:沒錯,這問題其實就是一個歐拉路的問題,不過和上一次不一樣的在於,這一次我們要找出一條歐拉路徑。

小Ho:那我們應該如何來找一條路徑呢?

小Hi:我們還是借用一下上次的例子吧

 

使用我們上一次證明歐拉路判定的方法,我們在這個例子中找到了2條路徑:

L1: 4-5-2-3-6-5
L2: 2-4-1-2

假設我們棧S,記錄我們每一次查找路徑時的結點順序。當我們找到L1時,棧S內的情況為:

S: 4 5 2 3 6 5 [Top]

此時我們一步一步出棧並將這些邊刪除。當我們到節點2時,我們發現節點2剛好是L1與L2的公共節點。並且L2滿足走過其他邊之后回到了節點2。如果我們在這個地方將L2先走一遍,再繼續走L1不就剛好走過了所有邊么。

而且在上一次的證明中我們知道,除了L1之外,其他的路徑L2、L3...一定都滿足起點與終點為同一個點。所以從任意一個公共節點出發一定有一條路徑回到這個節點。

由此我們得到了一個算法:

  1. 在原圖中找一個L1路徑

  2. 從L1的終點往回回溯,依次將每個點出棧。並檢查當前點是否還有其他沒有經過的邊。若存在則以當前點為起點,查找L2,並對L2的節點同樣用棧記錄重復該算法。

  3. 當L1中的點全部出棧后,算法結束。

在這里我們再來一個有3層的例子:

在這個例子中:

L1: 1-2-6-5-1
L2: 2-3-7-2
L3: 3-4-8-3

第一步時我們將L1壓入棧S,同時我們用一個數組Path來記錄我們出棧的順序:

S: [1 2 6 5 1]
Path:

然后出棧到節點2時我們發現了2有其他路徑,於是我們把2的另一條路徑加入:

S: 1 [2 3 7 2]
Path: 1 5 6

此時L2已經走完,然后再開始彈出元素,直到我們發現3有其他路徑,同樣壓入棧:

S: 1 2 [3 4 8 3]
Path: 1 5 6 2 7 

之后依次彈出剩下的元素:

S: 
Path: 1 5 6 2 7 3 8 4 3 2 1

此時的Path就正好是我們需要的歐拉路徑。

小Ho:原來這樣就能求出歐拉路,真是挺巧妙的。

小Hi:而且這個算法在實現時也有很巧妙的方法。因為DFS本身就是一個入棧出棧的過程,所以我們直接利用DFS的性質來實現棧,其偽代碼如下:

DFS(u):
	While (u存在未被刪除的邊e(u,v))
		刪除邊e(u,v)
		DFS(v)
	End
	PathSize ← PathSize + 1
	Path[ PathSize ] ← u

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 1005;
int n, m, flag, top, sum, du[N], ans[5005], map[N][N];

void dfs(int x)
{
    ans[++top] = x;
    for(int i = 1; i <= n; i++)
    {
        if(map[x][i] >= 1)
        {
            map[x][i]--;
            map[i][x]--;
            dfs(i);
            break;
        }
    }
}

void fleury(int x)
{
    top = 1;
    ans[top] = x;
    while(top > 0)
    {
        int k = 0;
        for(int i = 1; i <= n; i++)//判斷是否可擴展
        {
            if(map[ans[top]][i] >= 1)//若存在一條從ans[top]出發的邊  那么就是可擴展
            {k = 1; break;}
        }
        if(k == 0)//該點x沒有其他的邊可以先走了(即不可擴展), 那么就輸出它
        {
            printf("%d ", ans[top]);
            top--;
        }
        else if(k == 1)//如可擴展, 則dfs可擴展的哪條路線
        {
            top--;//這需要注意
            dfs(ans[top+1]);
        }
    }
}
int main()
{
    while(scanf("%d%d", &n, &m) != EOF)
    {
        memset(du, 0, sizeof(du));
        memset(map, 0, sizeof(map));

        for(int i = 1; i <= m; i++)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            map[x][y]++; //記錄邊, 因為是無向圖所以加兩條邊, 兩個點之間可能有多條邊
            map[y][x]++;
            du[x]++;
            du[y]++;
        }
        flag = 1; // flag標記開始點。 如果所有點度數全為偶數那就從1開始搜
        sum = 0;
        for(int i = 1; i <= n; i++)
        {
            if(du[i] % 2 == 1)
            {
                sum++;
                flag = i;// 若有奇數邊, 從奇數邊開始搜
            }
        }
        if(sum == 0 || sum == 2)
            fleury(flag);
    }
    return 0;
}

 

 


免責聲明!

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



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