尾遞歸

經過階乘計算來認識尾遞歸。階乘能夠用下面的表達式來描述:html

n!=n*(n-1)*(n-2)…3*2*1算法

根據上面的表達式咱們能夠歸納出下面的算法來計算階乘:編程

n!=n*(n-1)!c#

        public int Factorial(int number)
        {
            if (number == 1)
            {
                return 1;
            }

            var temp = number * Factorial(number - 1);

            return temp;
        }

函數調用:數據結構

var calculator=new Calculator();
var number = calculator.Factorial(6);

下面的替換模型描述了計算機是如何執行這一代碼的:函數式編程

當咱們使用一個過大的數值,例如求:Factorial(5000)將會發生StackOverFlowException。函數

爲了將它轉化爲一個尾遞歸函數,可使用一種提供「累加器參數」的技術,咱們須要向函數中添加一些參數,用來提供當前結果。3d

提供product參數,product=product*currentCounthtm

提供currentCount參數,currentCount=currentCount+1blog

下面的代碼描述了改造後的代碼:

        public int Factorial(int number)
        {
            return TailedFactorial(1,1, number);
        }

        public int TailedFactorial(int product, int currentNumber, int number)
        {
            if (currentNumber > number)
            {
                return product;
            }

            var temp = TailedFactorial(product*currentNumber, ++currentNumber, number);

            return temp;
        }

與前面同樣,咱們看看這段代碼的替換模型:

考慮第一個計算過程,這一過程展現了一個先逐步展開然後收縮 的形狀,在展開階段描述了計算機在每次調用時如何向堆棧中添加一個堆棧幀的,收縮階段則表現爲這些運算的實際執行。要執行這種計算過程,計算機就須要維護好那些之後將要執行的操做軌跡。

與之對應,第二個計算過程並無任何增加或收縮。對於任何一個n,在計算過程當中的每一步,全部的東西都保存在變量product,currentNumber和number中,在這種場景下,在計算過程當中的任何一點,這幾個變量都提供了有關計算狀態的一個完整描述。若是咱們令上述計算在某兩個步驟之間停下來,要想從新喚醒這一計算,只須要提供這三個變量便可。咱們稱這樣的特性爲尾遞歸。

遞歸在函數式編程中是絕對重要的,在尾遞歸技術的支持下,即便在極深度的遞歸調用中,也能夠避免堆棧溢出。

在函數式語言中還存在一種極其重要的數據結構:「列表」。關於函數式列表的一個重要事實就是它們是不可變的,也就意味着咱們能夠構造一個列表,但不能取得一個已有列表並修改它;也不能添加或刪除一個元素。這一數據結構極其適合經過遞歸的方式來處理,以F#爲例,針對一個列表編寫map和filter函數:

經過模式匹配處理列表的兩個分支:

let rec mapN f list=
    match list with
    | [] ->[]
    | x::xs-> let xs=(mapN f xs)
                f(x)::xs
let rec filterN f list=
    match list with
    | []->[]
    | x::xs->let xs=(filterN f xs)
               if f(x) then x::xs else xs

當咱們處理一個大型列表的時候,這兩個函數將會發生StackOverflowException.

let large=[1..100000]
large|>mapN (fun n->n*n)

解決這一問題的辦法是採用尾遞歸:

let map f list=
    let rec map' f list tempList=
        match list with
        |[]->List.rev(tempList)
        |x::xs->let tempList=f(x)::tempList
                map' f xs tempList
    map' f list []

在map函數內部添加一個累加器參數tempList,咱們在對列表進行迭代時收集元素並將它們存儲在累加器內,一旦到達告終尾部分,就能夠返回咱們已經收集的元素了。

filter函數的尾遞歸實現:

let filter f list=
    let rec filter' f list tempList=
        match list with
        |[]->List.rev(tempList)
        |x::xs->let tempList=if f(x) then x::tempList else tempList
                filter' f xs tempList
    filter' f list []

 

參考書籍:SICP

                       Real world Functional Programming with examples in F# and c#

相關文章
相關標籤/搜索