從約瑟夫問題的遞迴實現的問題說起

2021-06-03 01:51:16 字數 1124 閱讀 1681

在解決約瑟夫問題時,我比較推薦使用遞迴,因為遞迴實現的演算法**更短,邏輯也更清晰,然而很多人有乙個疑問,那就是他們知道遞迴層數是有極限的,這就意味著當需要很大層數的遞迴時,遞迴演算法是不可行的,會導致段錯誤。

對於這個問題我有三個回答:第一,只要你使用的是計算機而不是你的大腦,你就不要指望什麼是無限制的,計算機不是神,計算機裡別談無限。第二,雖然你的c**是遞迴實現的,但是編譯器生成的二進位制檔案卻不一定會遞迴呼叫乙個函式,這就涉及了編譯器的優化。第三,因為我們使用的大多數是x86體系的馮諾依曼機器,這種機器的編譯器一般對於函式呼叫是通過棧實現的,而我們知道棧的預備空間和最大擴充套件空間一般都不會太大,然而如果有一種機器,根本就沒有棧的概念,或者說即使在x86機器上實現乙個**的編譯器,將函式呼叫退化成簡單的jump而不使用棧操作,那麼棧的限制將不再存在。

在基於棧實現函式呼叫的機器上,我們看一下在什麼情況下必須層層巢狀用遞迴實現。其中乙個條件,那就是在遞迴的過程中,我們無法獲知任何計算的中間結果,必須等到最深入一層的函式滿足非遞迴條件而返回時,整個過程才會一層一層的返回,但是如果每一步的中間結果都被儲存或者說每一步的遞迴過程並不是要計算什麼值,而僅僅是為了實現乙個***,那麼最終的二進位制檔案大可不必非要用遞迴。而且我們知道,遞迴和迭代是等價的,這一點非常重要。

編譯器不是傻子-其實是實現編譯器的人很聰明,編譯器一定不會傻到非要用遞迴生成最終的二進位制執行檔案,以gcc為例,即使你在c函式中明確使用了遞迴,在-o3的優化編譯選項下,編譯器還是會嘗試將遞迴化為迭代的,所生成的二進位制objdump檔案中你將看不出任何遞迴呼叫,這種工作並不是你自己實現的,而是編譯器幫你實現的。

有一種遞迴被叫做「尾遞迴」,對於這種遞迴的理解其實很簡單,那就是每一步遞迴呼叫的中間結果被儲存,因此你可以只使用乙個棧幀實現所有的遞迴過程,因為中間結果隨著遞迴的深入而持續改變,因此你可以將其理解為一種隱含的迭代。對於約瑟夫問題,這種尾遞迴是很顯然的,因為每一步的遞迴過程的效果其實都是一種***,那就是在鍊錶上刪除乙個節點。但是你還是要告訴編譯器這一切,否則編譯器仍然使用遞迴過程生成二進位制**。

最終的結果,你用-o3選項編譯這個c檔案,使用objdump檢視其目標碼,你將看不到任何遞迴過程,這就突破了棧的限制,但是記住,限制仍然是有的,因為很抱歉,你用的是計算機這種奴隸式的工具,計算機說的最多的話,那就是:sorry, it is beyond my ablity!

從約瑟夫問題的遞迴實現的問題說起

在解決約瑟夫問題時,我比較推薦使用遞迴,因為遞迴實現的演算法 更短,邏輯也更清晰,然而很多人有乙個疑問,那就是他們知道遞迴層數是有極限的,這就意味著當需要很大層數的遞迴時,遞迴演算法是不可行的,會導致段錯誤。對於這個問題我有三個回答 第一,只要你使用的是計算機而不是你的大腦,你就不要指望什麼是無限制...

約瑟夫問題的實現

約瑟夫問題的實現 2000 ms 65535 kb 2816 8761 n個人圍成乙個圈,每個人分別標註為1 2 n,要求從1號從1開始報數,報到k的人出圈,接著下乙個人又從1開始報數,如此迴圈,直到只剩最後乙個人時,該人即為勝利者。例如當n 10,k 4時,依次出列的人分別為4 8 2 7 3 1...

約瑟夫問題的實現 swustoj

約瑟夫問題的實現 2000 ms 65535 kb 2662 8037 n個人圍成乙個圈,每個人分別標註為1 2 n,要求從1號從1開始報數,報到k的人出圈,接著下乙個人又從1開始報數,如此迴圈,直到只剩最後乙個人時,該人即為勝利者。例如當n 10,k 4時,依次出列的人分別為4 8 2 7 3 1...