最初讓我體會遞迴的就Joe的《Programming Erlang》,隨著對計算(computing)的理解,對遞迴的體會也會有所不同。這裡我記錄下我的理解過程。
1,理解遞迴
遞迴是需要List這種結構的,這也是為什麼Lisp本身就是LISt Processor表縮寫。資料是表,那麼我們可以一個個地處理表中的每個元素。處理方式也頗為簡單:
1,解決空表的特殊情況。
2,處理頭元素,一般是一個S-expressions(atom or list)。
3,遞迴處理尾元素。尾元素通常表示為:(cdr lst)。
上面這句話還可以再簡練些:
1,擦屁股
2,處理頭
3,遞迴尾
2,遞迴解構
能達到什麼樣要求的語言才可以實現遞迴?
很明顯,遞迴的函數必須接受List作為其參數(arguments),所以必須有類型List這樣的資料結構。其次,必須有first, rest這樣的no effect side資料返回提取操作。
我們來說說函數,其實函數是具名的Processor,除此之外他還表示高一層的抽象——他是一個處理過程。當一個函數(處理過程)接受List類型時,這個函數離“可遞迴”不遠了。
3,解決問題的遞迴
記得剛學物件導向時,當某些人說:“一功都是對象”。就會覺得某人牛逼,頓時崇拜有加。但隨著時間的深入,有些覺得挺扯蛋的。比如在一些控制操作比較多的應用中,如果你用OOP的話,那些你建立了一堆臆想的對象。這不就是為OOP而OOP嘛。
推理到這裡,遞迴也不適合解決所有問題。但確實有些問題從本質上就是遞迴的,比如快速排序,用Erlang來表示的話就是:
sort([Pivot|T]) ->
sort([ X || X <- T, X < Pivot]) ++
[Pivot] ++
sort([ X || X <- T, X >= Pivot]);
sort([]) –> [].
從完成任務的思想上來說:
1,擦屁股(當list為空白時直接返回空)。
2,處理頭(Pivot)。
3,遞迴尾(T < Pivot和T >= Pivot)。其實這裡更通俗的說法是處理剩下的,也就是比Pivot大的和比Pivot小的。
如果用自然語言來說比較繞,也就是把第1個元素當Pivot,然後將比Pivot小的放到Pivot前面,將比Pivot大的放到Pivot後面。怎麼知道是否比Pivot大?遞迴回去直到只有一個元素。
我這裡說這麼多無非就是遞迴只是一種抽象方式,也是看世界的一個角度。有時候從這個角度看世界很清晰,有時候根本看不清。可以嘗試從另一個角度觀察,盡量貼近問題域來解決問題,而不是解決方案域來解決問題。