在我接受到的教育中,遞歸似乎是一個很神秘的東西。它難以說清,是高級貨,像一團迷霧。在高中課外學習的過程中,我一開始沒有理解到什麼是遞歸,老師也可能覺得我們比較難以理解,所以後來就沒有統一說說。而我也覺得貌似挺難的,也貌似不用也可以,所以就沒了回事。後來到了大學,教編程語言的老師居然極度忽視遞歸,那時的我已經知道遞歸的重要性,她竟然說了些站不住腳的觀點就糊弄過去,說白了,很多都是語法糖衣。
直到看sicp的時候,我或許才真正感受到遞歸。
首先說明一下,sicp的展開基於一種lisp的方言——scheme語言,它沒有所謂的循環語句,因此從語法形式上看,所有的重復做某事的過程都是遞歸過程。那麼按照一般思維,它是否就不存在迭代呢?因為在很多命令式語言諸如C,java等都具有循環語句,所以我們一般寫迭代都是用循環語句來寫,而不會用函數遞歸來寫,這是比較容易混淆的一點。在scheme語言中,將這種基於具體計算過程的分類,分為遞歸計算過程和迭代計算過程。我們很多時候都基於語法形式而忽略計算模式來說,其實是不太准確的。
(下面是sicp中文版關於此的一段引文:
當我們說一個過程是遞歸的時候,論述的是一個語法形式上的事實,說明這個過程的定義中(直接或者間接地)引用了該過程本身。在說某一計算過程具有某種模式時(例如,線性遞歸),我們說的是這一計算過程的進展方式,而不是相應過程書寫上的語法形式。)
一般來說,遞歸計算過程呈現一種先擴展後收縮的計算模式,即需要解釋器(或編譯器)維護著某些信息,並會隨著擴展而增加。而迭代計算過程並沒有任何擴展或收縮,它只需要幾個固定的必要的信息。用階乘函數可以很簡潔地闡釋:
;遞歸計算過程
(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) ;迭代計算過程 (define (fact-iter counter result) (if (= counter 1) result (fact-iter (- counter 1) (* counter result)))) (define (factorial1 n) (fact-iter n 1)) ;遞歸計算過程 (define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) ;迭代計算過程 (define (fact-iter counter result) (if (= counter 1) result (fact-iter (- counter 1) (* counter result)))) (define (factorial1 n) (fact-iter n 1))
當然,遞歸計算過程還可以簡單分為線性遞歸計算過程(階乘)和樹形遞歸計算過程(斐波拉契)。顯然,樹形遞歸存在大量的冗余計算,但其特點是簡潔。
暫時先說這麼多,下一篇分別舉幾個例子來說明遞歸計算過程和迭代計算過程。