1. 程式人生 > 實用技巧 >帶你逐步分析遞迴演算法的時間複雜度

帶你逐步分析遞迴演算法的時間複雜度

> 更過乾貨文章持續更新,微信搜尋「程式碼隨想錄」第一時間圍觀,本位GitHub:https://github.com/youngyangyang04/TechCPP已經收錄,裡面有更多幹貨等著你,歡迎Star

很多同學對遞迴演算法的時間複雜度都不甚瞭解

同一道題目,同樣使用遞迴演算法,有的同學寫出了O(n)的程式碼,有的同學就寫出了O(logn)的程式碼

這是為什麼呢, 就是因為對遞迴的時間複雜度理解的不夠深入導致的

如果恰巧正在讀本文的你也對遞迴演算法的時間複雜度懵懵懂懂,請認真讀完本篇文章,一定會有所收穫

這裡我想通過一道簡單的面試題,來帶大家逐步分析遞迴演算法的時間複雜度,最後找出最優解。

來看一下這道面試題:求x的n次方

大家想一下這麼簡單的一道題目 程式碼應該如何寫。

最直觀的方式應該就是,一個for迴圈求出結果,程式碼如下

int function1(int x, int n) {
    int result = 1;  // 注意 任何數的0次方等於1
    for (int i = 0; i < n; i++) {
        result = result * x;
    }
    return result;
}

時間複雜度為O(n)

此時面試官會說,有沒有效率更好的演算法呢。

如果同學們此時沒有思路,建議不要說:我不會,我不知道。可以和麵試官探討一下,問:可不可以給點提示。

面試官一般會提示:考慮一下遞迴演算法

有的同學就寫出瞭如下這樣的一個遞迴的演算法,使用遞迴解決了這個問題

int function2(int x, int n) {
    if (n == 0) {
        return 1; // return 1 同樣是因為0次方是等於1的
    }
    return function2(x, n - 1) * x;
}

面試官問:那麼這份程式碼的時間複雜度是多少?

有的同學可能一看到遞迴就想到了logn,其實並不是這樣

遞迴演算法的時間複雜度本質上是要看:遞迴的次數 * 每次遞迴中的操作次數

那我們再來看程式碼,我們遞迴了幾次呢。

每次n-1,遞迴了n次 時間複雜度是O(n)

,每次進行了一個乘法操作,乘法操作的時間複雜度一個常數項O(1)

所以這份程式碼的時間複雜度是n * 1 = O(n)

這個時間複雜度可能就沒有達到面試官的預期。

於是同學又寫出了這樣的一個遞迴的演算法的程式碼如下 ,來求 x的n次方

int function3(int x, int n) {
    if (n == 0) {
        return 1;
    }
    if (n % 2 == 1) {
        return function3(x, n/2) * function3(x, n/2)*x;
    }
    return function3(x, n/2) * function3(x, n/2);
}

面試官看到後微微一笑,問這份程式碼的時間複雜度又是多少呢?

我們來分析一下

首先看遞迴了多少次呢,可以把遞迴的次數 抽象出一顆滿二叉樹。

我們剛剛寫的這個演算法,可以用一顆滿二叉樹來表示(為了方便表示 我選擇n為偶數),如圖:

當前這顆二叉樹就是求x的n次方,n為16的情況

n為16的時候 我們進行了多少次乘法運算呢

這棵樹上每一個節點就代表著一次遞歸併進行了一次相乘操作

所以 進行了多少次遞迴的話,就是看這棵樹上有多少個節點。

熟悉二叉樹的同學應該知道如何求滿二叉樹節點數量

這顆滿二叉樹的節點數量就是2^3 + 2^2 + 2^1 + 2^0 = 15

有同學就會發現 這其實是等比數列的求和公式, 如果不理解的同學可以直接記下來這個結論。

這個結論在二叉樹相關的面試題裡也經常出現。

這麼如果是求x的n次方,這個遞迴樹有多少個節點呢,如下圖所示

時間複雜度忽略掉常數項-1之後,我們發現這個遞迴演算法的時間複雜度依然是O(n)。

此時面試官就會問, 貌似這個遞迴的演算法依然還是O(n)啊, 很明顯沒有達到面試官的預期

那麼在思考一下O(logn)的遞迴演算法應該怎麼寫

這裡在提示一下 上面剛剛給出的那份遞迴演算法的程式碼,是不是有哪裡比較冗餘呢。

來看這份優化後的遞迴演算法程式碼

int function4(int x, int n) {
    if (n == 0) {
        return 1;
    }
    int t = function4(x, n/2);// 這裡相對於function3,是把這個遞迴操作抽取出來
    if (n % 2 == 1) {
        return t*t*x;
    }
    return t*t;
}

那我們看一下 時間複雜度是多少

依然還是看他遞迴了多少次

我們可以看到 這裡僅僅有一個遞迴呼叫,且每次都是 n/2

所以這裡我們一共呼叫了 log以2為底n的對數次

每次遞迴了做都是一次乘法操作,這也是一個常數項的操作,

所以說這個遞迴演算法的時間複雜度才是真正的O(logn)。

如果同學們最後寫出了這樣的程式碼並且時間複雜度分析的非常清晰,相信面試官是比較滿意的。

最後希望通過這麼一個簡單的面試題,讓大家真正瞭解了遞迴演算法的時間複雜度該如何分析。