成都小程序設計開發人員經常使用算法將問題分解為更容易解決的更小的塊。這種分而治之的方法使得為了解決每個問題,您可以多次調用同一個函數來處理每個部分。
在編程中,遞歸發生在方法調用自身時,并在達到基本情況時終止。基本情況是一個條件語句,它執行返回語句而不是再次調用相同的函數,結束循環。遞歸的典型用途包括分而治之算法和解決連續出現的問題,例如計算斐波那契數列或階乘。
在這篇文章中,我們將討論成都小程序設計中的遞歸,以及如何編寫計算數字階乘的遞歸方法。您將了解如何在成都小程序設計中使用遞歸,何時比其他方法更好,以及實現遞歸函數時的最佳實踐。
Java 中用于遞歸的代碼相對簡單,尤其是與迭代方法相比。遞歸可以幫助您編寫使用更少內存的軟件,因為一旦函數返回,變量就會被刪除。遞歸函數是純函數,這意味著它們的輸出僅取決于它們的輸入參數。
了解成都小程序設計中遞歸的最簡單方法之一是檢查打印數字階乘的函數。
您可以通過將一個數乘以所有小于它本身的正整數來計算階乘。在本節中,您將看到遞歸代碼與基于循環的代碼之間的比較。
例如,5 的階乘為 120,可以這樣表示:
階乘 (5) = 5 * (4 * (3 * (2 * 1)))
請注意這是如何形成一個級數的:5 的階乘等于 5 乘以 4 的階乘,4 乘以 3 的階乘,依此類推。遞歸的基本情況是 0。當輸入參數達到 0 時,您將從方法主體返回 1。
以下函數計算作為參數傳遞的數字的階乘——一次使用 For 循環,然后再次使用遞歸。在主入口點調用此方法并傳遞一個參數。您可以通過提供 5 作為輸入參數來對此進行測試,程序返回 120。
您可以使用 For 和 While 循環在成都小程序設計中執行階乘。
在構建遞歸函數時,您可以添加邊緣情況來縮短遞歸。如果下一個遞歸調用將是基本情況,則進行短路測試。您可以在數字等于或小于 0 時返回 2 并開始遞歸,而不是僅在數字等于或小于 0 時返回 1。
請記住,短路需要編寫一個包裝函數來對參數執行條件檢查。這通常是不鼓勵的,因為包裝器的價值需要更高。
為了處理短路,向類中添加一個新的成員方法作為遞歸函數的包裝器。包裝函數 factorial 調用內部方法factorialRecursive開始遞歸。此代碼具有相同的輸出,但在執行過程中又跳過了一步,因為當數字變為 2 時它會短路。
現在讓我們使用“遞歸和階乘”部分中的方法來研究決定遞歸和非遞歸方法的一些因素。
在成都小程序設計中,遞歸以多種方式提高性能,包括:
記憶化
Memoization 跳過已經計算輸出并存儲在內存中的遞歸情況。這可以防止重復計算,因為存儲了輸出并提高了軟件的性能。這種方法利用緩存來提高使用遞歸的性能。
查找階乘的代碼使用緩存變量來存儲以前使用的值。
此外,遞歸方法還僅依賴于輸入并包含業務邏輯,而沒有底層技術方面,例如堆棧管理。這允許工程師編寫具有更少內存和更少副作用(方法范圍之外的狀態更改,例如系統參數)的軟件。
此外,它對特定算法很有用,例如樹遍歷。深度優先搜索算法使用堆棧來執行搜索。因此,您可以將算法編寫為遞歸函數,與迭代方法相比,這更容易編寫。例如,比較使用遞歸和迭代方法的前序遍歷代碼。
最后,一些表達式,尤其是在數學運算中使用的表達式,具有特定的符號。這些操作的輸入最常見的表示法是前綴、中綴和后綴。固定是操作數在操作中的位置。這允許遞歸函數輕松復制操作而無需額外的包裝器。您可以使用上述表達式從數組構建樹,反之亦然。這有助于在一維內存結構(例如 RAM)中表示復雜的數據結構,例如樹。
遞歸也有其局限性。首先,遞歸函數反復調用自身,這會導致堆棧因參數溢出而導致程序過早終止。在成都小程序設計中,每個程序的堆棧空間都是有限的,而堆的限制則較少。因此,當程序試圖使用大量堆棧空間時,它會收到 StackOverflowException,此時程序會繼續壓入堆棧但不會彈出并達到限制。
另一方面,堆不是后進后出操作 (LIFO),因此程序可以在系統允許的范圍內向堆推送盡可能多的數據。
此外,當遞歸函數執行函數調用作為最后一個語句時,Java 編譯器無法優化使用尾遞歸的遞歸方法。相反,遞歸函數將函數調用作為頭遞歸中的第一條語句執行。
“處理邊緣情況”部分中的代碼演示了沒有一個函數是尾遞歸。factorialRecursion 是非尾遞歸(不要與頭遞歸混淆),因為它作為函數的結果運行。
此方法使用一個變量來存儲所有數字的乘積。它運行一個循環,該循環從我設置為2的變量開始并返回產品。請注意,大于 2 的數字必須相乘,直到i等于數字參數本身。當滿足循環條件時,迭代停止。
迭代風格使得定義迭代計數、內存管理和計算停止的時間變得更加容易。這也允許更好地控制堆棧增長。它有助于避免成都小程序設計程序中的 StackOverflowException。
同樣,迭代方法不需要包裝函數。因此,它們避免了任何不需要的額外堆棧輸入。這就是為什么通常不鼓勵通過縮短遞歸來獲得一次性性能改進的原因。
然而,對于基于序列的問題,例如計算階乘或斐波那契數列,迭代方法通常更難編寫。遞歸通常會產生一種更優雅的方法,它可以用更少的代碼行產生相同的結果。
在遞歸函數中,處理所有可能的邊緣情況以從遞歸函數返回。如果您不處理邊緣情況,您的遞歸可能會永遠運行,導致您的程序由于 StackOverflowException 錯誤而過早終止。遞歸中的短路并不總是最好的方法,因為您通常必須圍繞遞歸函數編寫包裝函數。代替短路包裝函數,對遞歸方法內部的邊緣情況和函數可以接受的參數應用條件檢查。
此外,請記住堆棧不會跟蹤已處理的參數。這會導致您的遞歸函數重復處理相同的參數。為避免這種重復并降低時間復雜度,您應該始終在處理后使用記憶來存儲參數。
遞歸函數允許成都小程序設計程序中的代碼調用自身,通過對一系列輸入參數運行相同的操作來計算輸出。所涵蓋的示例只是其實際應用的一小部分。Java 軟件開發工具包 (SDK) 中的各種搜索和排序算法都使用遞歸,包括深度優先搜索、歸并排序和樹遍歷。
這并不是說遞歸是萬能的方法,尤其是在內存有限的情況下。在這種情況下,建議使用迭代方法來編寫函數。這使得解決方案更具可擴展性并且不易發生內存溢出。
然而,遞歸使成都小程序設計的軟件工程師能夠利用函數式編程的最佳實踐并將它們應用于面向對象的編程而沒有副作用。這是一種更聰明的方法,而不是更難。