1. 程式人生 > >怎麼更好地終極理解遞迴演算法【轉】

怎麼更好地終極理解遞迴演算法【轉】

遞迴真是個奇妙的思維方式。對一些簡單的遞迴問題,我總是驚歎於遞迴描述問題和編寫程式碼的簡潔。但是總感覺沒能融會貫通地理解遞迴,有時嘗試用大腦去深入“遞迴”,層次較深時便常產生進不去,出不來的感覺。這種狀態也導致我很難靈活地運用遞迴解決問題。有一天,我看到一句英文:“To Iterate is Human, to Recurse, Divine.”中文譯為:“人理解迭代,神理解遞迴。”然後,我心安理得地放棄了對遞迴的深入理解。直到看到王垠談程式語言最精華的原理時提到了遞迴,並說遞迴比迴圈表達能力強很多,而且效率幾乎一樣。再次喚醒了我對遞迴的理解探索。

    我首先在知乎上發現了下面兩個例子,對比了遞迴和迴圈。

    遞迴:你打開面前這扇門,看到屋裡面還有一扇門(這門可能跟前面開啟的門一樣大小(靜),也可能門小了些(動)),你走過去,發現手中的鑰匙還可以開啟它,你推開門,發現裡面還有一扇門,你繼續開啟,。。。, 若干次之後,你打開面前一扇門,發現只有一間屋子,沒有門了。 你開始原路返回,每走回一間屋子,你數一次,走到入口的時候,你可以回答出你到底用這鑰匙開了幾扇門。

    迴圈:你打開面前這扇門,看到屋裡面還有一扇門,(這門可能跟前面開啟的門一樣大小(靜),也可能門小了些(動)),你走過去,發現手中的鑰匙還可以開啟它,你推開門,發現裡面還有一扇門,(前面門如果一樣,這門也是一樣,第二扇門如果相比第一扇門變小了,這扇門也比第二扇門變小了(動靜如一,要麼沒有變化,要麼同樣的變化)),你繼續開啟這扇門,。。。,一直這樣走下去。 入口處的人始終等不到你回去告訴他答案。

該使用者這麼總結到:

    遞迴就是有去(遞去)有回(歸來)。

具體來說,為什麼可以”有去“? 

    這要求遞迴的問題需要是可以用同樣的解題思路來回答除了規模大小不同其他完全一樣的問題。

為什麼可以”有回“?

  這要求這些問題不斷從大到小,從近及遠的過程中,會有一個終點,一個臨界點,一個baseline,一個你到了那個點就不用再往更小,更遠的地方走下去的點,然後從那個點開始,原路返回到原點。

上面的解釋幾乎回答了我已久的疑問:為什麼我老是有遞迴沒有真的在解決問題的感覺?

    因為遞是描述問題,歸是解決問題。而我的大腦容易被遞佔據,只往遠方去了,連盡頭都沒走到,何談回的來。

《漫談遞迴:遞迴的思想》這篇文章將遞迴思想歸納為:

    遞迴的基本思想是把規模大的問題轉化為規模小的相似的子問題來解決。在函式實現時,因為解決大問題的方法和解決小問題的方法往往是同一個方法,所以就產生了函式呼叫它自身的情況。另外這個解決問題的函式必須有明顯的結束條件,這樣就不會產生無限遞迴的情況了。

    需注意的是,規模大轉化為規模小是核心思想,但遞歸併非是隻做這步轉化,而是把規模大的問題分解為規模小的子問題和可以在子問題解決的基礎上剩餘的可以自行解決的部分。而後者就是歸的精髓所在,是在實際解決問題的過程。

    我試圖把我理解到遞迴思想用遞迴用程式表達出來,確定了三個要素:遞 + 結束條件 + 歸。

function recursion(大規模)

{

if (end_condition)

{

end;     

}

else

{     //先將問題全部描述展開,再由盡頭“返回”依次解決每步中剩餘部分的問題

recursion(小規模);     //go;

solve;                //back;

}

}

    但是,我很容易發現這樣描述遺漏了我經常會遇到的一種遞迴情況,比如遞迴遍歷的二叉樹的先序。我將這種情況用如下遞迴程式表達出來。

function recursion(大規模)

{

if (end_condition)

{

end;     

}

else

{     //在將問題轉換為子問題描述的每一步,都解決該步中剩餘部分的問題。

solve;                //back;

recursion(小規模);     //go;

}

}

    總結到這裡,我突然發現遞迴是為了最能表達這種思想,所以用“遞迴”這個詞,其實遞迴可以是“有去有回”,也可以是“有去無回”。但其根本是“由大往小地去,由近及遠地去”。“遞”是必需,“歸”並非必需,依賴於要解決的問題,有的需要去的路上解決,有的需要回來的路上解決。有遞無歸的遞迴其實就是我們很容易理解的一種分治思想。

    其實理解遞迴可能沒有“歸”,只有去(分治)的情況後,我們應該想到遞迴也許可以既不需要在“去”的路上解決問題,也不需要在“歸”的路上解決問題,只需在路的盡頭解決問題,即在滿足停止條件時解決問題。遞迴的分治思想不一定是要把問題規模遞迴到最小,還可以是將問題遞迴窮舉其所有的情形,這時通常遞迴的表達力體現在將無法書寫的巢狀迴圈(不確定數量的巢狀迴圈)通過遞迴表達出來。

將這種遞迴情形用遞迴程式描述如下:

recursion()

{

if (end_condition)

{

solve;     

}

else

{     //在將問題轉換為子問題描述的每一步,都解決該步中剩餘部分的問題。

for () { recursion();     //go; }

}

}

    由這個例子,可以發現這種遞迴對遞迴函式引數出現了設計要求,即便遞迴到盡頭,組合的字串規模(長度)也沒有變小,規模變小的是遞迴函式的一個引數。可見,這種變化似乎一下將遞迴的靈活性大大地擴充套件了,所謂的大規模轉換為小規模需要有一個更為廣義的理解了。

    對遞迴的理解就暫時到這裡了,可以看出文章中提到關於“開啟一扇門”的遞迴例子來解釋遞歸併不準確,例子只描述了遞迴的一種情況。而“遞迴就是有去(遞去)有回(歸來)”的論斷同樣不夠準確。要為只讀了文章前半部分的讀者惋惜了。我也給出自己對遞迴思想的總結吧:

    遞迴的基本思想是廣義地把規模大的問題轉化為規模小的相似的子問題或者相似的子問題集合來解決。廣義針對規模的,規模的縮小具體可以是指遞迴函式的引數,也可以是其引數之一。相似是指解決大問題的方法和解決小問題的方法往往是同一個方法,還可以是指解決子問題集的各子問題的方法是同一個方法。解決大問題的方法可以是由解決次規模問題的方法和解決剩餘部分的方法組成,也可以是由一系列解決次規模問題的方法組成。

--------------------- 本文來自 積矽步至千里 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/StruggleShu/article/details/51051140?utm_source=copy