1. 程式人生 > >高次遞迴轉換為迭代方式實現後 對計算效率的影響

高次遞迴轉換為迭代方式實現後 對計算效率的影響

遞迴轉換為迭代方式實現對計算效率的影響

什麼是遞迴

一句話解釋遞迴:自己呼叫自己

遞迴(百度百科)

eg.

define function(x)
{
    if x ... {
        return ...
    } else {
        return function(x...)...
    }
}

舉例:階乘、斐波拉契數列

define Factorial(x)
{
    if x < 1 {
        return error
    } else if x == 1 {
        return 1
    } else {
        return x * Factorial(x-1)
    }
}

define Fibonacci(x)
{
    if x < 1 {
        return error
    } else if x == 1 || x == 2 {
        return 1
    } else {
        return Fibonacci(x-1) + Fibonacci(x-2)
    }
}

什麼是迭代

一句話解釋迭代:每次結果都是由上次結果進行相同的運算得到

迭代(百度百科)

eg.

define function(res, count, x)
{
    if count, x ... {
        return res
    } else {
        return function(res ..., count ..., x)
    }
}

其呼叫需要給出初始態,由初始態向後計算達到需要的狀態。由於初始態固定,其呼叫時rescount為固定值,所以大多時候都會選擇做進一步封裝。

舉例:階乘、斐波拉契數列

define factorial(res, count, x)
{
    if x < 1 {
        return error
    } else if count < x {
        return factorial(count * res, count + 1, x)
    } else {
        return res
    }
}

define fibonacci(res, res_1, count, x)
{
    if x < 1 {
        return error
    } else if x == 1 || x == 2 {
        return 1
    } else if count < x {
        return fibonacci(res + res_1, res, count + 1, x)
    } else {
        return res
    }
}

其呼叫方式為factorial(1, 1, x)fibonacci(1, 1, 2, x),進一步封裝為

define Factorial(x)
{
    return factorial(1, 1, x)
}

define Fibonacci(x)
{
    return fibonacci(1, 1, 2, x)
}

遞迴方式與迭代方式的計算效率對比

對比計算效率的差異,最簡單的方式當然是實際跑程式碼了。

廢話少說,show you my code。這裡使用python,以斐波拉契數列為例,比較兩種方式計算耗時。

遞迴方式及迭代方式的實現如下:

def Recursion(x):
    if x < 1:
        return None
    elif x == 1 or x == 2:
        return 1
    else:
        return Recursion(x-1) + Recursion(x-2)

def iteration(result, result_1, count, x):
    if x < 1:
        return None
    elif x == 1 or x == 2:
        return 2
    elif count < x:
        return iteration(result + result_1, result, count+1, x)
    else:
        return result

def Iteration(x):
    return iteration(1, 1, 2, x)

測試函式:

def test(msg, func, x, times):
    start = time.clock()
    for index in range(times):
        func(x)
    end = time.clock()
    print('Computing the %dth number of Fibolacci, %s running [%7d] times, time cost: %s Seconds'%(x, msg, times, end-start))

測試結果:

>>>test("Recursion",Recursion,30,1)
>Computing the 30th number of Fibolacci, Recursion running [ 1] times, time cost: 0.1959006259738926 Seconds

>>>test("Iteration",Iteration,30,1000)
>Computing the 30th number of Fibolacci, Iteration running [ 1000] times, time cost: 0.0047303810543576075 Seconds

>>>test("Recursion",Recursion,40,1)
>Computing the 40th number of Fibolacci, Recursion running [ 1] times, time cost: 23.777185128182722 Seconds

>>>test("Iteration",Iteration,40,1000)
>Computing the 40th number of Fibolacci, Iteration running [ 1000] times, time cost: 0.0064214635387642716 Seconds

由於兩種實現方式在計算時間上的巨大差異,測試函式裡面寫了計算n次,迭代方式計算1000次計算總時間,不然數量級相差太大不好比較。python畢竟是使用直譯器的指令碼語言,用編譯語言可能會更快一點。

計算效率差距為何如此之大

剛才實際跑程式碼看了一下他們的耗時差距,在運算第30位時其實耗時還比較少,沒什麼明顯的感覺。但是運算第40位的時候遞迴方式使用了二十多秒才計算出來,等待這麼久才有輸出,都不需要認真做對比。那到底是什麼導致了它們的差距會這麼大呢?

我們可以從程式碼實現上來分析一下原因。

  1. 遞迴方式

    遞迴方式的實現為函式為Recursion,我們看一下Recursion的時間複雜度:
    函式內部的運算時間複雜度為O(1),加上函式跳轉消耗的時間遠大於這幾個運算消耗的時間,我們其實只需要關注發生了幾次遞迴呼叫就可以了。在count < x的條件判斷分支,可以看到發生了兩次遞迴呼叫,每多計算一輪需要多發生兩次遞迴呼叫,所以這個函式的時間複雜度應該是O(2^n)。時間複雜度呈指數增長,如果用它來計算斐波拉契數列的第50位,我們很有可能會被耗死在電腦前。

  2. 迭代方式

    迭代方式的實現為函式為Iteration,是對iteration的二次封裝,那我們也一樣,分析一下iteration的時間複雜度:
    同上,函式內部的運算時間複雜度為O(1),我們一樣只需要關注發生了幾次遞迴呼叫。在count < x的條件判斷分支,可以看到發生了一次遞迴呼叫,每多計算一輪需要多發生一次遞迴呼叫,所以這個函式的時間複雜度是O(n)。時間複雜度呈線性增長,很明顯優於遞迴方式的指數增長。

那分析完複雜度,其實就很明白了,為什麼我要用斐波拉契數列舉例,而不用階乘。因為階乘的遞迴實現也只發生了一次遞迴呼叫,和迭代實現一樣,時間複雜度也為O(n)。所以如果用階乘舉例是看不到什麼優化效果的。如果有人有興趣,也可以自己寫程式碼對比一下階乘的兩種實現方式是不是真的沒有太大差異。

同時,分析完複雜度,也能很清晰的明白,當需要我們使用遞迴的時候,什麼情況下需要對遞迴進行迭代方式的優化,什麼時候不需要。
當發生高次遞迴呼叫的時候,可以將實現方式切換為迭代,使方法的時間複雜度由指數級優化到線性級;而在一次遞迴的情況下,就可以不做額外轉換,畢竟遞迴方式的程式碼可讀性實在是比迭代方式友好太多了。