3月 07, 2010

【演算】遞迴函式 - Recursive Function

@
  所謂的遞迴(recursion),簡單來說就是「函式(function)不斷呼叫自身」的一種程式撰寫法。遞迴的意義,在於把一個問題切割成相同性質的較小問題來解決。而為了防止程式無窮盡的遞迴下去,我們還必須為所寫出來的遞迴函式設定一個終止條件(termination condition)

  遞迴的原理,是利用函式本身的堆疊(stack)性質--後進先出(last in, first out,LIFO)來達成的。說得簡單一點,就是當某個函式呼叫另一個函式時,其會先執行完被呼叫的函式,才繼續執行自身的函式內容。



  讓我們舉一個常見的例子來解釋:階乘(factorial)

  在數學上的定義中,對於一個非負整數 n,其階乘代表的是所有小於或等於 n 的正整數乘積,記作 n!。即 n! = 1 × 2 × 3 × ... × n。若我們規定 0! = 1,則可以將之改寫成:對於所有非負整數 n,(n + 1)! = (n + 1) × n!。

  於是,我們便可以利用程式寫出計算階乘的遞迴函式。以下為其虛擬碼:

Function factorial(Integer n)
    If n = 0 then
        Return 1
    Else
        Return n × factorial(n - 1)
End

  在這裡,我們相當於將原始的問題 n! 看作是 n × (n - 1)! 這個較小的問題。也就是得知 (n - 1)! 的值,就可以求得 n! 的解。同樣的,又可以將 (n - 1)! 看作是 (n - 1) × (n - 2)!、將 (n - 2)! 看作是 (n - 2) × (n - 3)!、......。

  舉例來說,若是我們需要利用程式求出 6! 的值。則在我們呼叫 factorial(6) 時,為了得出結果,亦須求出 factorial(5) 的值。而為了得出 factorial(5) 的結果,我們亦須求出 factorial(4) 的值......。一直不斷遞迴下去,直到達到終止條件:n = 0 為止。

  實際運作的結果,就如下圖所示意的(藍箭頭代表呼叫,紅箭頭代表回傳):





  除此之外,遞迴還可以解決相當多其他的問題。例如斐波那契數列(fibonacci sequence)老鼠走迷宮(mouse in a maze)、以及河內塔(tower of hanoi)等等,這裡我就不加贅述了。



  雖然利用程式的遞迴解法較為直觀,但是不斷堆疊函式的結果,極有可能浪費了過多的記憶體空間。其實,遞迴的程式多半都能藉由迴圈的方式替代,只是程式也可能會因此變得複雜難懂。至於實際上要使用何者,就看你怎麼選擇囉。

7 回覆:

GuestBook上請教的小弟 提到...

個人覺得遞迴非常難用= =尤其是到了很複雜的時候 連自己都不知道該怎麼做
比較起來 迴圈解似乎直觀(個人認為啦)而且效能高一點
一直都很好奇 像這樣子比較耗資源及時間的解法 在大型程式上會不會被使用呢?

另外 貓抓老鼠的程式我有看過囉 不過...總覺得好像比較接近DFS..找最快路徑是不是用Queue+陣列會比較好做呢?

Unknown 提到...

這樣喔,我個人是認為遞迴通常較迴圈直覺。
在大型程式上,我想應該都會選擇較節省資源與時間的方式吧。
頂多就是,在某些關鍵點上於空間與時間上做出取捨。

貓抓老鼠,是指老鼠走迷宮嗎?
應用遞迴來找路徑,其實跟 DFS 本質上是相同的。
至於利用 Queue 來找,應該是 BFS 的方式吧?
這兩者各有適合使用的情況,也不能直接斷定何者較好。

chchwy 提到...

我覺得fibonacci其實不算好例子,因為非遞迴寫法也很簡單,甚至比遞迴解更簡單直覺。以致於學習者常認為,我有好好的迴圈作法不做,何必來弄這個難搞的方法?

演算法中常常見到的 Devide&Conquer 式的思想,才是遞迴的精華,先把大問題切成小問題,然後從小問題集合成大問題的解,錮中核心就在遞迴關係式上。寫得出遞迴關係式,問題往往就迎刃而解了。

一點點小看法@_@"

Unknown 提到...

其實我舉的例子是 factorial.....XD

一開始舉這個例子,是為了簡單介紹遞迴的想法
原因我身邊多數人都認為遞迴很難懂,故想以此來做個簡單的介紹

我認同你所說的,D&C 為遞迴精華的觀點
在文章中我也有提到這點,不過這個例子看來好像真的不太明顯 XD

總之,感謝你的意見囉:)

匿名 提到...

遞迴@@
那疊代的虛擬碼呢?

LAI YEN WEI (Linus) 提到...

謝謝小參的教學,每一篇都非常直覺易懂!!

Unknown 提到...

這個... gettoken是否..很像遞迴的觀念?
Public Function GetToken(new_txt As String, delimiter As String) As String
Static txt As String
Dim pos As Integer

' Save new text.
If new_txt <> "" Then txt = new_txt

pos = InStr(txt, delimiter)
If pos < 1 Then pos = Len(txt) + 1
GetToken = Left$(txt, pos - 1)
pos = Len(txt) - pos + 1 - Len(delimiter)
If pos < 1 Then
txt = ""
Else
txt = Right$(txt, pos)
End If
End Function

張貼留言