Farmer John每一年有不少柵欄要修理。他老是騎着馬穿過每個柵欄並修復它破損的地方。c++
John是一個與其餘農民同樣懶的人。他討厭騎馬,所以歷來不兩次通過一個柵欄。你必須編一個程序,讀入柵欄網絡的描述,並計算出一條修柵欄的路徑,使每一個柵欄都剛好被通過一次。John能從任何一個頂點(即兩個柵欄的交點)開始騎馬,在任意一個頂點結束。算法
每個柵欄鏈接兩個頂點,頂點用1到500標號(雖然有的農場並無500個頂點)。一個頂點上可鏈接任意多(>=1)個柵欄。兩頂點間可能有多個柵欄。全部柵欄都是連通的(也就是你能夠從任意一個柵欄到達另外的全部柵欄)。編程
你的程序必須輸出騎馬的路徑(用路上依次通過的頂點號碼錶示)。咱們若是把輸出的路徑當作是一個500進制的數,那麼當存在多組解的狀況下,輸出500進製表示法中最小的一個 (也就是輸出第一位較小的,若是還有多組解,輸出第二位較小的,等等)。網絡
輸入數據保證至少有一個解。函數
輸入格式:spa
第1行: 一個整數F(1 <= F <= 1024),表示柵欄的數目翻譯
第2到F+1行: 每行兩個整數i, j(1 <= i,j <= 500)表示這條柵欄鏈接i與j號頂點。code
輸出格式:blog
輸出應當有F+1行,每行一個整數,依次表示路徑通過的頂點號。注意數據可能有多組解,可是隻有上面題目要求的那一組解是認爲正確的。遞歸
9 1 2 2 3 3 4 4 2 4 5 2 5 5 6 5 7 4 6
1 2 3 4 2 5 4 6 5 7
題目翻譯來自NOCOW。
USACO Training Section 3.3
Solution:
來系統地整理一下有關歐拉回路和歐拉路徑的問題。
什麼是歐拉路徑?在圖上用一種走法通過全部的邊一次且只有一次的路徑叫作歐拉路徑。即一筆畫。
若是這條路徑的起點和終點重合,那麼就是歐拉回路。
如何判斷圖是否有歐拉回路或者歐拉路徑?
無向圖:由於歐拉路徑中,除了起點與終點之外,任意點的「進」「出」次數相等,因此除了兩個點爲奇點(度數爲奇數的點)(終點和起點)之外,其它點的度數均爲偶數。
若是是歐拉回路,奇點的個數應該爲0。
有向圖:歐拉路徑中,最多隻有兩個點的入度不等於出度。起點出度比入度大1,終點入度比出度大1。
若是是歐拉回路,全部點的 入度=出度 。
尋找歐拉回路或歐拉路徑的算法有?
Fluery算法和Hierholzer算法。後者好像也有博客稱逐步插入迴路法。
後面一種算法不管是編程複雜度仍是時間複雜度好像都比前種算法複雜度更優,但前者的應用普遍性好像比後者更高。歐拉回路的更高級的應用還沒涉及,若是以後有什麼新內容再來補充吧....因此這裏就用Hierholzer算法了。
Hierholzer算法自動尋找歐拉回路,在找不到歐拉回路的狀況下會找到歐拉路徑。前提是得給它指定好起點。
算法流程(無向圖):
1.判斷奇點數。奇點數若爲0則任意指定起點,奇點數若爲2則指定起點爲奇點。
2.開始遞歸函數Hierholzer(x):
循環尋找與x相連的邊(x,u):
刪除(x,u)
刪除(u,x)
Hierholzer(u);
將x插入答案隊列之中
3.倒序輸出答案隊列
對於該圖,算法的執行流程以下:
1.找到該圖沒有奇點,從1開始進行Hierholzer算法。
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算法和歐拉路徑更高級應用的坑。(大概)