測試開發基礎之算法(1):複雜度分析方法

時間複雜度表示代碼的執行時間隨着數據規模增長的趨勢。使用O()表示
【假設】每一行代碼執行的時間都一樣,都是unit_time。所有代碼的總執行時間 T(n) 與每行代碼的執行次數成正比。

1、只關注循環執行次數最多的一段代碼

def calc(n):
    """
    分析這個代碼執行時間,(3+2n)*unit_time,去掉常量,去掉高階的係數,從而得出時間複雜度就是O(n)
    """
    total = 0  # 執行1次
    for i in range(0, n + 1):  # 執行n+1次
        total = total + i  # 執行n+1次
    return total

再舉一例子:

def combination(data: list):
    """
    從一個列表中每次取出兩個,找出所有組合方式。
    分析這個代碼執行時間,(1+n+2n*n)*unit_time,去掉常量、低階、高階的係數,從而得出時間複雜度就是O(n*n)
    思考:前後順序不同的組合算同一個,比如(1,3)和(3,1)算一個組合,該如何寫?
    """
    n = len(data)  # 執行1次
    for x in range(0, n):  # 執行n次
        for y in range(0, n):  # 執行n*n次
            print(data[x], data[y])  # 執行n*n次

2、加法法則

總複雜度等於量級最大的那段代碼的複雜度。

def addition_law(n):
    """
    加法法則。綜合這三段for循環代碼的時間複雜度,我們取其中最大的量級。所以,整段代碼的時間複雜度就爲 O(n*n)。
    :param n:
    :return:
    """
    sum_1 = 0
    sum_2 = 0
    sum_3 = 0
    # 第一段
    for p in range(100):
        sum_1 = sum_1 + p  # 常量的執行時間,跟 n 的規模無關
    # 第二段
    for q in range(n):
        sum_2 = sum_2 + q  # 時間複雜度是O(n)
    # 第三段
    for i in range(n):
        for j in range(n):
            sum_3 = sum_3 + i * j  # 時間複雜度是O(n*n)

    return sum_1 + sum_2 + sum_3

3、乘法法則

嵌套代碼的複雜度等於嵌套內外代碼複雜度的乘積。

def multiplication_law(n: int):
    """
    乘法法則,嵌套代碼的複雜度等於嵌套內外代碼複雜度的乘積。T1(n) * T2(n) = O(n*n) = O(n*n)。
    """
    ret = 0

    def f(n):
        total = 0
        for j in range(n):
            total = total + j  # 執行n次
        return total

    for i in range(n):
        ret = ret + f(i)  # 執行n次,調用f(i) n次
    return ret

4、對數階複雜度

def geometric_progression(n: int) -> int:
    """
    打印等比數列
    i從1開始,每次循環乘以2,當大於n時結束循環。i其實是個等比數列,公比是2。循環次數是log2n,時間複雜度就是 O(log2n)。
    不管是以 2 爲底、以 3 爲底,還是以 10 爲底,我們把所有對數階的時間複雜度都記爲 O(logn)
    """
    i = 1
    while i <= n:
        i = i * 2
        print(i)

5、複雜度由兩個數據的規模來決定

def dependent_on_two_scale(m, n):
    """
    從代碼中可以看出,m 和 n 是表示兩個數據規模。我們無法事先評估 m 和 n 誰的量級大,所以我們在表示複雜度的時候,就不能簡單地利用加法法則,省略掉其中一個。
    代碼的複雜度由兩個數據的規模來決定m和n。時間複雜度就是 O(m+n)
    """
    sum_1 = 0
    sum_2 = 0
    for i in range(m):
        sum_1 = sum_1 + i
    for i in range(n):
        sum_2 = sum_2 + i

    return sum_1 + sum_2

6、遞歸算法時間複雜度分析

遞歸算法是時間複雜度超高的。

def fibonacci(n):
    """
    求fibonacci的第n項。
    遞歸算法複雜度分析。畫遞歸樹,比如fibonacci(6)如圖。複雜度爲O(2^N)
    """
    if n == 0:
        return 0
    if n in [1, 2]:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)

7、優化遞歸算法降低時間複雜度

def fibonacci_plus(n: int) -> int:
    """
    斐波那契數列也可以從左到右依次求出每一項的值,那麼通過順序計算求得第N項即可。其時間複雜度爲O(N)。
    """
    a, b = 0, 1
    if n < 2:
        return n
    for _ in range(2, n+1):
        a, b = b, a + b
    return b

8、最好、最差時間複雜度

代碼在不同情況下的不同時間複雜度。最好情況時間複雜度就是,在最理想的情況下,執行這段代碼的時間複雜度。最壞情況時間複雜度就是,在最糟糕的情況下,執行這段代碼的時間複雜度。

def search(data: list, x: int) -> int:
    """
    從無序數組中查找某個元素的下標。最好時間複雜度是O(1),最差時間複雜度是 O(n)
    要查找的變量 x 可能出現在數組的任意位置。
    如果數組中第一個元素正好是要查找的變量 x,那就不需要繼續遍歷剩下的 n-1 個數據了,那時間複雜度就是 O(1)。
    但如果數組中不存在變量 x,那我們就需要把整個數組都遍歷一遍,時間複雜度就成了 O(n)。
    :param data: 無重複元素無序數組
    :param x: 待尋找的元素
    :return: x的下標
    """
    length = len(data)
    for i in range(length):
        if data[i] == x:
            return i
        else:
            return -1

9、加權平均時間複雜度

上面介紹了最好和最差的時間複雜度,都是比較極端的情況。那麼平均的時間複雜度是多少呢?

查找變量x在data中的位置,有n+1中可能,處於0~n-1的下標,和不在data中。我們把每種情況下,查找時需要遍歷的元素個數累加起來,然後再除以 n+1,就可以得到需要遍歷的元素個數的平均值。(1+2+3…+n+n)/(n+1),省略掉係數、低階、常量,所以,得到的平均時間複雜度就是 O(n)。

前面的推導過程中存在的最大問題就是,沒有將各種情況發生的概率考慮進去。在數組中與不在數組中的概率都爲 1/2。另外,要查找的數據出現在 0~n-1 這 n 個位置的概率也是一樣的,爲 1/n。
如果我們把每種情況發生的概率也考慮進去,那平均時間複雜度的計算過程就變成了這樣:
在這裏插入圖片描述
用大 O 表示法來表示,去掉係數和常量,這段代碼的加權平均時間複雜度仍然是 O(n)。

10、空間複雜度分析

就看額外分配了多少內存。

def space_complexity(n: int):
    """
    第 1 行申請了一個大小爲 n 的 列表,除此之外,剩下的代碼都沒有佔用更多的空間,所以整段代碼的空間複雜度就是 O(n)。
    """
    space = [0] * n  # 申請了一個大小爲 n 列表
    for i in range(n):
        space[i] = i * i
    print(space)

11、總結

複雜度分析的實用方法:

  1. 只關注循環執行次數最多的一段代碼;
  2. 加法法則:總複雜度等於量級最大的那段代碼的複雜度;
  3. 乘法法則:嵌套代碼的複雜度等於嵌套內外代碼複雜度的乘積
  4. 複雜度由兩個數據的規模來決定m和n時,時間複雜度是 O(m+n)
  5. 常見的時間複雜度:O(1)<O(logn)<O(n)<O(nlogn)<O(n*n)
  6. 空間複雜度分析比較簡單,就看額外分配了多少內存就好。

12、參考文獻

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章