尾調用、尾遞歸 與 非尾遞歸

尾調用、尾遞歸、非尾遞歸的幾個概念及非尾遞歸是否能夠轉換爲尾遞歸。html

一、 尾調用:

tail call指的是一個函數的最後一條語句也是一個返回調用函數的語句,即return的是一個函數,這個函數能夠是另外一個函數,也能夠是自身函數。
def fun_b(x):
  x = x -1
  return x

def function_a(x):
  x = x + 1
  return fun_b(x) # 最返回的是函數

二、尾遞歸:

函數在return的時候調用自身,這種狀況稱爲尾遞歸,除了調用自身不能有其餘額外的。尾遞歸是遞歸的一種特殊形式,也是是尾調用的一種特殊形式(尾調用最後調用函數自身,就是一個尾遞歸)。尾調用不必定是尾遞歸。

尾遞歸特色:python

  1. 在迴歸過程當中不用作任何操做,編譯器會利用這種特色自動生成優化的代碼。(編譯器發如今迴歸調用時經過覆蓋當前的棧,而不是建立新的棧,使用來減小棧空間的使用和減小棧的分配釋放開銷)
  2. 尾遞歸是把變化的參數傳遞給遞歸函數的變量了。遞歸發生時,外層函數把計算結果當成參數傳給內存函數。
  3. 尾遞歸的優化從函數調用的彙編實現很容易理解,只要不涉及多餘的參數(也就是不用額外的棧容量存,棧頂不用往下擴展再調用回函數),直接就能夠改變%edi,%esi的值,從新調用函數,也就是說不用任何額外的贊變量。
def fun_b(x):
  if x == 1:
    return x
  else:
    return fun_b(x) # 最後返回調用的是自身函數,是尾遞歸,若是寫成`return fun_b(x)+1`則不是尾遞歸

三、非尾遞歸:

把尾遞歸以外的其餘遞歸調用歸結爲非尾遞歸

四、棧調用的區別

普通遞歸函數自身調用次數不少,遞歸層級很深,棧空間爲0(n),尾遞歸優化後棧空間可爲O(1)。下面有例子能夠觀察其棧佔用的不一樣:函數

# 這個是一個非尾遞歸
def normal_sum(x):
  if x == 1:
    return x
  else:
    return x + normal_sum(x - 1)

調用fun_sum(5)非尾遞歸的棧變化:學習

normal_sum(5)
5 + normal_sum(4)
5 + (4 + normal_sum(3))
5 + (4 + (3 + normal_sum(2)))
5 + (4 + (3 + (2 + normal_sum(1))))
5 + (4 + (3 + (2 + 1)))
5 + (4 + (3 + 3))
5 + (4 + 6)
5 + 10
15

把上面的函數修改成尾遞歸的方式(尾遞歸是把變化的參數傳遞給遞歸函數的變量了):優化

def tail_sum(x, total=0):
  if x == 0:
    return total
  else:
    return tail_sum(x - 1, total + x)

調用尾遞歸tailrecsum(5, 0)的棧變化:code

tail_sum(5, 0)
tail_sum(4, 5)
tail_sum(3, 9)
tail_sum(2, 12)
tail_sum(1, 14)
tail_sum(0, 15)
15

四、非尾遞歸是否已能夠轉回爲尾遞歸

經過CPS變換,能夠把非尾遞歸轉換爲尾遞歸。其核心思想就是把
能夠參考這篇博客orm

終身學習!
相關文章
相關標籤/搜索