[英]What is the definition of “natural recursion”?
在構建列表時,描述第一個典型元素,然后將其納入自然遞歸。
“自然遞歸”的確切定義是什么? 我問的原因是因為我正在接受Daniel Friedman的編程語言原則課程,以下代碼不被認為是“自然遞歸”:
(define (plus x y)
(if (zero? y) x
(plus (add1 x) (sub1 y))))
但是,以下代碼被認為是“自然遞歸”:
(define (plus x y)
(if (zero? y) x
(add1 (plus x (sub1 y)))))
我更喜歡“非自然遞歸”代碼,因為它是尾遞歸的。 但是,這樣的代碼被認為是詛咒。 當我問到為什么我們不應該以尾遞歸形式編寫函數時,副教師簡單地回答說:“你不要亂用自然遞歸。”
以“自然遞歸”形式編寫函數有什么好處?
“自然”(或“結構”)遞歸是開始教學生遞歸的最佳方式。 這是因為它有Joshua Taylor指出的美妙保證:它保證終止[*]。 學生們有足夠的時間圍繞這種程序包圍他們,這使得這個“規則”可以為他們節省大量的頭撞牆。
當你選擇離開結構遞歸的領域時,你(程序員)承擔了額外的責任,這是為了確保你的程序在所有輸入上停止; 還有一件事要考慮和證明。
在你的情況下,它有點微妙。 你有兩個參數,並且你在第二個參數上進行結構遞歸調用。 實際上,通過這種觀察(程序在結構上在參數2上是遞歸的),我認為你的原始程序與非尾部調用程序一樣合法,因為它繼承了相同的收斂證明。 問丹這件事; 我很想知道他要說些什么。
[*]在這里,你必須立法規定各種其他愚蠢的東西,比如對不終止的其他功能的調用,等等。
自然遞歸與您正在處理的類型的“自然”遞歸定義有關。 在這里,你正在使用自然數; 因為“顯然”自然數是零或另一個自然數的后繼,當你想建立一個自然數時,你自然地輸出0
或(add1 z)
一些其他自然的z
,恰好是遞歸計算的。
教師可能希望您在遞歸類型定義和遞歸處理該類型的值之間建立鏈接。 如果你試圖處理樹或列表,你不會遇到數字問題,因為你經常以“不自然的方式”使用自然數字,因此,你可能會對教會數字有自然的反對意見。
您已經知道如何編寫尾遞歸函數的事實在這種情況下是無關緊要的: 至少就目前而言 ,這顯然不是您的老師談論尾部調用優化的目標。
副教練起初並不是很有幫助(“亂搞自然遞歸”的聲音為“不要問”),但他/她在你給的快照中給出的詳細解釋更合適。
(define (plus x y)
(if (zero? y) x
(add1 (plus x (sub1 y)))))
當y != 0
,必須記住,一旦(plus x (sub1 y))
的結果已知,就必須在其上計算add1
。 因此,當y達到零時,遞歸最深。 現在回溯階段開始,執行add1
。 可以使用trace
來觀察此過程。
我做了以下追蹤:
(require racket/trace)
(define (add1 x) ...)
(define (sub1 x) ...)
(define (plus x y) ...)
(trace plus)
(plus 2 3)
這是跟蹤:
>(plus 2 3)
> (plus 2 2)
> >(plus 2 1)
> > (plus 2 0) // Deepest point of recursion
< < 2 // Backtracking begins, performing add1 on the results
< <3
< 4
<5
5 // Result
不同之處在於其他版本沒有回溯階段。 它調用自己幾次,但它是迭代的,因為它記住了中間結果(作為參數傳遞)。 因此,該過程不會消耗額外的空間。
有時實現尾遞歸過程比使用迭代等效函數更容易或更優雅。 但出於某些目的,您不能/不能以遞歸方式實現它。
PS:我有一個關於垃圾收集算法的課程。 這樣的算法可能不是遞歸的,因為可能沒有剩余空間,因此沒有用於遞歸的空間。 我記得一個名為“Deutsch-Schorr-Waite”的算法,起初真的很難理解。 首先,他實現了遞歸版本只是為了理解這個概念,之后他編寫了迭代版本(因此手動必須記住他來自內存的位置),相信我的遞歸方式更容易但不能在實踐中使用...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.