通過棧,所有遞歸都能變成循環。高階語言只是把語言層級的遞歸變成實作層級的堆疊。這個問題在網路上有很詳細的論文。用google搜尋recursion iteration會發現相當多的答案。
用堆疊來實現將遞歸變成循環並不能帶來效能的最佳化,在同種語言的條件下比較更可能帶來效能的損失。
有的遞歸不需要增加棧。去掉了堆疊和堆疊操作可以帶來速度和空間兩方面的效率提升。這其中的一類叫尾遞歸,有的語言實現了尾遞歸優化,也就是自動將尾遞歸變成循環同時不增加棧。例如lisp,scheme,haskell等很多語言都實現了這種優化,但是大多數動態語言包括python,javascript,ruby都沒有,因此這些語言經常會報告遞歸函數超過最大棧深度的錯誤。
我正在做的一個語言,其中一個特性是在支援尾遞歸的優化的同時還要支援某些非尾遞歸的優化,將遞歸函數轉換成不用棧的循環(當然只是一部分,因為不用棧是不可能將所有函數轉換成循環的)。
如sum = (x) -> if x=0 then return 0 else return x+f(x-1)
這個函數是非尾遞歸的,因為遞歸呼叫函數之後還做了加法。
轉換成尾遞歸是這種形式:sum = (x, acc) -> if x=0 then return acc else return f(x-1, acc)
現在的某些語言會自動把後面這種函數變成循環,同時堆疊不增加。也就是類似如下的程式碼
sum = (x, acc) -> while 1 if x=0 then return acc else acc += x
我做的語言將自動把非尾遞歸的版本變成如下的循環:
sum = (x) -> sum = 0; while 1 then if x=0 then return sum else x--; f += x
補充說明一點: 我這裡提供的並不是偽代碼,全部都是我將要發布的語言中的合法代碼。
能。大學計科有講這個的。而且反過來也成立。
你想啊,遞歸呼叫就是把參數壓棧。改成循環,我手動建個棧,每次循環把需要的資料壓進去,循環完彈出來不就可以了麼。
遞迴 vs 循環:
當然能。
所謂遞歸無非就是函數(直接或間接)呼叫自己,所以我們先看一下簡單的函數呼叫。
假設有一個函數長這樣:
你可以把它變成這樣:
其中
gcalloc
就是指從GC heap上分配一個什麼東西,這裡分配的是一個map,用於保存foo的stack frame。然後,foo可以變成這樣:
這裡的
continuation
你可以認為就是一個“返回地址”,這個等下再說。你很可能會問,這麼一大堆變換到底是乾神馬?
嗯,其實這一堆就只變換乾了一件事,那就是──把所有的函式呼叫變成tail call了。
大家都知道,tail call是可以直接優化成goto的,所以我們必須記錄原本的回傳位址,把它傳遞給被tail call的函數,這樣它就可以在結束的時候直接回到最初的呼叫者那裡。
如果對每個函數都做這樣的變換,讓每個函數呼叫都是tail call,那我們就徹底幹掉了stack,整個程式只用goto就搞定。
PS. 對上面內容有興趣的童鞋可以去看看 http://en.wikipedia.org/wiki/Continuation-passing_style
PPS. 其實從「理解」這個角度而言,遞歸往往更容易更清晰,因為它天生就是一個把高複雜度問題分解為低複雜度的過程。
如果上面兩個問題的答案都是yes,那就得到了一個構造證明方法。
通過棧,所有遞歸都能變成循環。高階語言只是把語言層級的遞歸變成實作層級的堆疊。這個問題在網路上有很詳細的論文。用google搜尋recursion iteration會發現相當多的答案。
用堆疊來實現將遞歸變成循環並不能帶來效能的最佳化,在同種語言的條件下比較更可能帶來效能的損失。
有的遞歸不需要增加棧。去掉了堆疊和堆疊操作可以帶來速度和空間兩方面的效率提升。這其中的一類叫尾遞歸,有的語言實現了尾遞歸優化,也就是自動將尾遞歸變成循環同時不增加棧。例如lisp,scheme,haskell等很多語言都實現了這種優化,但是大多數動態語言包括python,javascript,ruby都沒有,因此這些語言經常會報告遞歸函數超過最大棧深度的錯誤。
我正在做的一個語言,其中一個特性是在支援尾遞歸的優化的同時還要支援某些非尾遞歸的優化,將遞歸函數轉換成不用棧的循環(當然只是一部分,因為不用棧是不可能將所有函數轉換成循環的)。
如sum = (x) -> if x=0 then return 0 else return x+f(x-1)
這個函數是非尾遞歸的,因為遞歸呼叫函數之後還做了加法。
轉換成尾遞歸是這種形式:sum = (x, acc) -> if x=0 then return acc else return f(x-1, acc)
現在的某些語言會自動把後面這種函數變成循環,同時堆疊不增加。也就是類似如下的程式碼
sum = (x, acc) -> while 1 if x=0 then return acc else acc += x
我做的語言將自動把非尾遞歸的版本變成如下的循環:
sum = (x) -> sum = 0; while 1 then if x=0 then return sum else x--; f += x
補充說明一點: 我這裡提供的並不是偽代碼,全部都是我將要發布的語言中的合法代碼。
不只loop能夠取代recursion, 而且還可以根據recursion的公式,直接推倒Genrating Function求值.
比如說Fibonacci, f(n)=f(n-1)+f(n-2),他的closed form就是 F(n) = n / (1-n-n^2)
參考:
http://en.wikipedia.org/wiki/Fibonacci_number
http://www.austinrochford.com/posts/2013-11-01-generating-functions-and-fibonacci- numbers.html
http://www.math.cmu.edu/~af1p/Teaching/Combinatorics/Slides/Generating-Functions.pdf
不覺得遞迴更像數學公式麼,
f(n)=f(n-1)+f(n-2)
如果寫成循環不見得好理解哦
理論上上行。但是,程式碼好理解更重要
遞迴是執行速度特別慢的演算法,個人建議,能用遞迴盡量不用遞迴。
如:f(n)=f(n-1)+f(n-2)
當n=100時,程序就跑不起了,,個別說更大的了。 。 。
大家可以嘗試
吧遞歸寫成循環,需要把函數表達式寫成
f(x)=x的多项式
形式,簡單的遞歸還行,複雜的遞歸化簡起來相當費勁,已經超出了程式設計師的力所能及範疇了。PHP遞迴是有深度限制的