以前說到,遞歸是一種將大問題分解爲小問題的解決方案。通常來講,遞歸被稱爲函數自身的調用。這麼說可能聽起來很奇怪,事實上在遞歸中,函數確實必須調用本身。git
例如在數學中,咱們都知道「階乘」的概念。例如5的階乘就是5*4*3*2*1
。github
咱們能夠總結出求n的階乘的規律,即 n! = n * (n -1) !算法
這就體現了遞歸。你能夠從中發現,咱們把求5的階乘一步一步轉化成了另一個個的小問題。數據結構
function factorial(int $n): int { if ($n = 0) { return 1; } return $n * factorial($n - 1); }
看上面的代碼,咱們能夠看到對於階乘問題的解決方案咱們有一個基礎的條件就是當n爲0的時候,咱們返回1。若是不符合這個條件,咱們返回n
乘 factorial(n)
,這符合遞歸特性的第一條和第三條。咱們避免了循環調用,由於咱們把每一次的遞歸調用都分解成了大問題的一個小的子問題。上面的算法思想能夠表達成:數據結構和算法
上面的遞歸代碼咱們一樣可使用迭代的方法實現函數
function factorial(int $n): int { $result = 1; for ($i = $n; $i > 0; $i--) { $result*= $n; } return $result; }
若是一個問題能夠很容易的使用迭代來解決,咱們爲什麼要使用遞歸?優化
遞歸是用來處理更加複雜的問題的,不是全部的問題均可以簡單的使用迭代來解決的。遞歸使用函數調用來管理調用棧,因此相比於迭代遞歸會使用更多和時間以及內存。此外,在迭代中,咱們每一步都會有一個結果,可是在遞歸中咱們必須等到base case執行結束纔會有任何結果。看上面的例子,咱們發如今遞歸算法中咱們沒有任何變量或者聲明來保存結果,而在迭代算法中,咱們每一次都用$result來保存了返回結果。spa
在數學中,斐波那契數列是一個特殊的整數數列,數列中的每個數的是由另外兩個數求和產生的。規則以下:code
function fibonacci($n) { if ($n == 0) { return 0; } if ($n == 1) { return 1; } return fibonacci($n - 1) + fibonacci($ - 2); }
另一個使用遞歸算法的常見問題是求兩個數的最大公因數。blog
function gcd(int $a, int $b) { if ($b == 0) { return $a; } return gcd($b, $a % $b); }
在每一次遞歸調用中,函數只調用本身一次,這就叫作線性遞歸。
在二分遞歸中,每一次遞歸調用函數調用本身兩次。求解斐波那契數列的算法就是二分遞歸,除此以外還有二分查找、分治算法、歸併排序等也使用了二分遞歸。
當一個遞歸返回的時候沒有等待的操做的時候就稱爲尾遞歸。斐波那契算法中,返回值須要乘之前一個遞歸的返回值,所以他不是尾遞歸,而求解最大公因式的算法是尾遞歸。尾遞歸是線性遞歸的一種形式。
例如在每一次遞歸調用中 有 A() 調用 B(), B() 調用 A() ,這樣的遞歸就叫作相互遞歸。
當一個遞歸函數把本身做爲一個參數進行遞歸調用時,就叫作嵌套遞歸。一個常見的栗子就是阿克曼函數,看下面的表達。
看最後一行的,能夠看到第二個參數就是遞歸函數本身。
下一篇內容會使用遞歸解決一些實際開發中會遇到的問題,例如構建N級分類、構建嵌套評論、目錄文件的遍歷等等。
PHP基礎數據結構專題系列目錄地址:地址 主要使用PHP語法總結基礎的數據結構和算法。還有咱們平常PHP開發中容易忽略的基礎知識和現代PHP開發中關於規範、部署、優化的一些實戰性建議,同時還有對Javascript語言特色的深刻研究。