歐拉回路

為什麼要寫這篇博客,因爲剛好在看離散數學的時候看到了歐拉回路,所以打算在OJ上面找一題練手,那麼順便也寫一下這個知識點。

上次寫字典序和全排列的那篇博客花費了不少的時間……我在考慮這篇要不也寫得簡短一點?但是這樣又好像沒有了效果。意識流吧。

——————正文——————

歐拉回路是說對於一個給定的圖,你可以找到一條迴路,使得其經過了任何一條邊,且不會出現重複。

歐拉通路就是對於一個給定的圖,找到一條通路,使得其經過了任何一條邊,且不會出現重複。

用書本的話描述,就是:

歐拉回路:恰好包含圖的每條邊一次的迴路

歐拉通路:恰好包含圖的每條邊一次的通路

兩者的區別在於:歐拉回路的起點與終點是同一個點,但是歐拉通路就不一定了。

歐拉通路/歐拉回路 最常見的益智例子就是——一筆畫


網上,手機上非常多這種一筆畫的遊戲,可能你會覺得一個能夠順利斬下各種智力遊戲的人很牛B很酷,但是我覺得吧沒什麼比程序員敲幾下鍵盤然後讓電腦自動玩那個遊戲更帥……就像這種就是程序員的傑作:


還有數獨這類遊戲也是,比較好玩的就是我之前些了一個解數獨的程序(很多人應該寫過),然後拿同學的手機來直接幫他通關……不好意思一直跑題,意思就是我們可以開始寫一個專門玩“一筆畫”這類遊戲的程序了。當然寫之前先要知道怎麼找歐拉通路/歐拉回路。

————判斷方法————

怎麼判斷一個圖裏面是否存在歐拉回路?好了直接抄書本的結論了:

結論1:在連通多重圖裏存在歐拉回路當且僅當每個頂點都有偶數度

同樣地怎麼樣判斷一個圖裏面是否存在歐拉通路?也直接抄棘輪了:

結論2:在連通多重圖裏存在歐拉通路當且僅當之多兩個頂點有奇數度


結論1的具體證法可以查一下書,比較感性的證明就是:

必要性:首先從某一頂點a開始,出發時度減少1,每次經過任何頂點,頂點的度都減少2,而最終回到a時,度減少1,這樣很容易知道歐拉回路每個頂點的度都是偶數,這樣就證明了歐拉回路存在的必要性

充分性:但是是不是每個頂點的度都是偶數就一定存在歐拉回路呢,就是說“偶數度”這個條件是不是充分?接着我們對一個每個頂點都有偶數度的圖G,構造一條歐拉回路。首先我們對G,從頂點a開始找一條儘可能長的迴路,用e表示圖的邊,v表示圖的頂點,那麼這條迴路可以這樣表示—— a e1 v1 e2 v2 ... v(n-1) en a,現在這條迴路我們稱爲A,這個時候,如果G中有任何一條邊不在A中,那麼就是說A不是一條歐拉回路,不過木有關係,我們這個時候一定能在 v1,v2...v(n-1),a 這n個頂點中,找到一個頂點 vx,其關聯的邊不在A中(爲神馬?因爲這個圖是連通的,如果A的頂點中所有關聯邊都在A中,那麼A就是要找的歐拉回路)。對於vx,我們以其爲起點,繼續尋找新的迴路B,要注意的是,之前已經走過的邊不能再走了。這個時候,我們就要問——為什麼你一定能從這個點開始找到迴路?因爲每個頂點有偶數度,對於任何一個頂點,目前仍然剩下偶數個度(無論頂點是否在A中),又要問一個問題——在尋找回路B的時候不會出現死衚衕嗎?不會,從vx頂點開始,其所連接的剩下任何一個頂點都肯定具有偶數度,這些頂點所連接的頂點肯定也是有偶數度(除非連接的是vx),因此不會出現“沒路走”的情況。好了,我們就這樣又找到了迴路B,我們還可以依照這樣的做法,一直找回路C,迴路D...直到沒有任何頂點的邊是不在迴路A,B,C,D...裏面的。找到這些迴路之後,神奇的事情就發生了,我們可以拼接這些迴路!而且一定是可以拼得起來的,如下圖A和B:


然後拼接是這樣的,將回路A從vx處打開,迴路B也從vx處打開,然後接起來就可以了,如下圖所示:


這樣就是一條新的迴路,為什麼可以這樣拼接?這個問題就比較簡單了,各位自己思考一下就可以知道了。

按照這樣的拼接,最終所有的迴路A,B,C,D之類的,全部可以拼成一條大回路,這樣到最後我們就可以得到一條歐拉回路,充分性得證

這樣結論1就感性地證明完了,歡呼一下吧,hooray~!


然後就是結論2了,這個的證明建議大家可以自己先想一下,大致也是通過先驗證證明必要性,再構造證明充分性,練習一下結論1和結論2的區別(將兩個奇數頂點之間連起來會怎麼樣?),就應該比較有思路了。這裏就不寫了。


————代碼————

代碼非常簡短~但是吧,我只寫了找歐拉回路的代碼,歐拉通路的還沒有寫,遲點心血來潮再寫吧。

在有向圖裏找歐拉回路的代碼裏面,有一點是非常巧妙的,就是你怎麼樣執行“拼接”這個過程?我曾經寫了一個較爲複雜的代碼,提交到評測系統時總是出錯,我也不知道為什麼,後來借鑑了網上的寫法,代碼長度瞬間縮短几倍……誇張吧。代碼的思路是這樣的,同樣也是從某個點開始,使用遞歸的方式找路,如果那條邊還沒有經過,就走那條邊,在遞歸的最後一層,當前的節點肯定已經是沒有任何邊是沒有走過的,回到原來的點了,還記得經過節點時度的變化嗎?因爲到達這一層的時候如果發現這個節點沒有出度,那麼就是說回到起始點了,那麼就可以返回到遞歸的上一層,在上一層裏,以該層作爲另外一個迴路的起點,實行遞歸(有點感覺了沒?),那麼輸出呢?我們可以在每一層的最後輸出,那麼輸出的順序就和走的順序是相反的,各位可以自行調整過來(例如不輸出而是使用壓棧的方式)。大致代碼如下,edge是鄰接表,tmp是個臨時的變量,index是當前這一層的節點編號。大家要練下手可以看一下這條題目(POJ 2230):點擊打開鏈接


vector<int> edge[MAXN];
void euler(int index)
{
	for(int i=0;i<edge[index].size();i++)
	{
          tmp=edge[index][i];
	  if(tmp==-1)
	    continue;
          edge[index][i]=-1;
          euler(tmp);
	}
	printf("%d\n",index);
}

——————完——————

呼,累死了,如果文章有不妥的地方,歡迎指出。


******<轉載說明>******

轉載註明:誠實的偷包賊

原文地址:http://blog.csdn.net/fanfank/article/details/9048683

******<轉載說明/>******


發佈了61 篇原創文章 · 獲贊 1 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章