1. 程式人生 > >[模板][持續更新]歐拉回路與歐拉路徑淺析

[模板][持續更新]歐拉回路與歐拉路徑淺析

bits solution 算法 -1 要求 logs 鏈式前向星 namespace src

Luogu P2731 騎馬修柵欄 Riding the Fences

題目背景

Farmer John每年有很多柵欄要修理。他總是騎著馬穿過每一個柵欄並修復它破損的地方。

題目描述

John是一個與其他農民一樣懶的人。他討厭騎馬,因此從來不兩次經過一個柵欄。你必須編一個程序,讀入柵欄網絡的描述,並計算出一條修柵欄的路徑,使每個柵欄都恰好被經過一次。John能從任何一個頂點(即兩個柵欄的交點)開始騎馬,在任意一個頂點結束。

每一個柵欄連接兩個頂點,頂點用1到500標號(雖然有的農場並沒有500個頂點)。一個頂點上可連接任意多(>=1)個柵欄。兩頂點間可能有多個柵欄。所有柵欄都是連通的(也就是你可以從任意一個柵欄到達另外的所有柵欄)。

你的程序必須輸出騎馬的路徑(用路上依次經過的頂點號碼表示)。我們如果把輸出的路徑看成是一個500進制的數,那麽當存在多組解的情況下,輸出500進制表示法中最小的一個 (也就是輸出第一位較小的,如果還有多組解,輸出第二位較小的,等等)。

輸入數據保證至少有一個解。

輸入輸出格式

輸入格式:

第1行: 一個整數F(1 <= F <= 1024),表示柵欄的數目

第2到F+1行: 每行兩個整數i, j(1 <= i,j <= 500)表示這條柵欄連接i與j號頂點。

輸出格式:

輸出應當有F+1行,每行一個整數,依次表示路徑經過的頂點號。註意數據可能有多組解,但是只有上面題目要求的那一組解是認為正確的。

輸入輸出樣例

輸入樣例#1:
9
1 2
2 3
3 4
4 2
4 5
2 5
5 6
5 7
4 6
輸出樣例#1:
1
2
3
4
2
5
4
6
5
7

說明

題目翻譯來自NOCOW。

USACO Training Section 3.3

Solution:

來系統地整理一下有關歐拉回路和歐拉路徑的問題。

什麽是歐拉路徑?在圖上用一種走法經過所有的邊一次且只有一次的路徑叫做歐拉路徑。即一筆畫。

如果這條路徑的起點和終點重合,那麽就是歐拉回路。

如何判斷圖是否有歐拉回路或者歐拉路徑?

無向圖:因為歐拉路徑中,除了起點與終點以外,任意點的“進”“出”次數相等,所以除了兩個點為奇點(度數為奇數的點)(終點和起點)以外,其它點的度數均為偶數。

如果是歐拉回路,奇點的個數應該為0。

有向圖:歐拉路徑中,最多只有兩個點的入度不等於出度。起點出度比入度大1,終點入度比出度大1。

如果是歐拉回路,所有點的 入度=出度 。

尋找歐拉回路或歐拉路徑的算法有?

Fluery算法和Hierholzers算法。後者好像也有博客稱逐步插入回路法。


後面一種算法無論是編程復雜度還是時間復雜度好像都比前種算法復雜度更優,但前者的應用廣泛性好像比後者更高。歐拉回路的更高級的應用還沒涉及,如果之後有什麽新內容再來補充吧....所以這裏就用Hierholzers算法了。

Hierholzers算法自動尋找歐拉回路,在找不到歐拉回路的情況下會找到歐拉路徑。前提是得給它指定好起點。

算法流程(無向圖):

1.判斷奇點數。奇點數若為0則任意指定起點,奇點數若為2則指定起點為奇點。

2.開始遞歸函數Hierholzers(x):
  循環尋找與x相連的邊(x,u):
    刪除(x,u)
    刪除(u,x)
    Hierholzers(u);
  將x插入答案隊列之中

3.倒序輸出答案隊列

技術分享

對於該圖,算法的執行流程如下:
1.找到該圖沒有奇點,從1開始進行Hierholzers算法。
2.刪邊1-2 遞歸到2
3.刪邊2-3 遞歸到3
4.刪邊3-7 遞歸到7
5.刪邊7-1 遞歸到1
6.1無邊,1加入隊列,返回
7.7加入隊列,返回
8.刪邊3-4 遞歸到4
9.刪邊4-5 遞歸到5
10.刪邊5-6 遞歸到6
11.刪邊6-3 遞歸到3
12.3加入隊列,返回
13.6加入隊列,返回
14.5加入隊列,返回
15.4加入隊列,返回
16.3加入隊列,返回
17.2加入隊列,返回
18.1加入隊列,返回

答案隊列為:1 7 3 6 5 4 3 2 1。反向輸出即為答案。

有向圖除判斷是否存在有一點點不同以外同理。

對於該題【歐拉路徑/歐拉回路】模板題,要求輸出答案的最小序列。所以起點首先要選的盡量小,然後在邊的儲存上面加一點小trick。
使用鄰接表儲存圖時,除了用鏈式前向星還可以用vector儲存。我們可以把vector換成multiset,這樣就可以保證該點前往的下一個點是最小值,同時保證了答案的最小值。

下面是該題的代碼:

#include<bits/stdc++.h>
using namespace std;
const int N=1025;
multiset<int> to[N];
int len[N];
int road[N],k;
void dfs(int x){
    for(auto a=to[x].begin();a!=to[x].end();a=to[x].begin()){//auto類型為C++11標準,可進行自動類型推斷 
        int u=*a;
        to[x].erase(a);
        to[u].erase(to[u].find(x));//刪邊 
        dfs(u);//遞歸 
    }
    road[k++]=x;//往答案隊列裏插入答案 
}

int main(){
    int m,a,b;
    scanf("%d",&m);
    for(int i=0;i<m;i++){
        scanf("%d%d",&a,&b);
        len[a]++,len[b]++;
        to[a].insert(b);
        to[b].insert(a);
    }
    int s=-1,e=-1;//起點與終點 
    for(int i=1;i<=1024;i++)
        if(len[i]%2==1){
            if(s==-1)s=i;
            else if(e==-1)e=i;
            else exit(1);
        }//判斷每個點的度數 
    if(s==-1)s=1;
    dfs(s);//開始遞歸 
    for(k=k-1;k>=0;k--)
        printf("%d\n",road[k]);//倒序輸出答案 
    return 0;
}

之後會填Fluery算法和歐拉路徑更高級應用的坑。(大概)

[模板][持續更新]歐拉回路與歐拉路徑淺析