每日一問之算法的時間複雜度

寫在前面

這周調整了下計劃,鑑於很多不懂的知識需要大量的時間去消化及整理輸出,因此,改爲每逢節假日更新每日一問。

今天來整理算法複雜度的相關知識。在算法中包含兩種複雜度,一種是時間複雜度,另一種是空間複雜度。這篇文章主要總結 時間複雜度 相關的知識點。

  • 時間複雜度
  • 大 O 表示法
  • 計算時間複雜度

時間複雜度

  • 時間頻度
    在計算機中,不可能真正的去計算算法的每條語句的執行時間,只有真正上機測試才能知道大概時間。但是實際工作中,不可能把所有的算法都運行測試一遍,這不現實也浪費時間。我們只需要知道算法中主要部分的運行時間就可以了。我們都知道一個算法的運行時間與算法中語句的執行次數成正比,所以將一個算法中的語句的執行次數稱爲語句頻度或時間頻度,記爲 T(n)。n 爲問題的規模,時間頻度會隨着 n 的變化而變化。

  • 時間複雜度
    在計算機科學中,時間複雜度(Time Complexity)是一個定性描述運行算法所花費的時間的度量。常用大 O 表示法來度量時間複雜度,記爲 T(n) = O(f(n))

在時間頻度 T(n) 中,n 又代表着問題的規模,當 n 不斷變化時,T(n) 也會不斷地隨之變化。爲了瞭解這個變化的規律,時間複雜度這一概念就被引入了。一般情況下算法基礎操作的重複執行次數爲問題規模 n 的某個函數,也就是時間頻度 T(n)。如果有某個輔助函數 f(n),當趨於無窮大的時候,T(n)/f(n) 的極限值是不爲零的某個常數,那麼 f(n) 是 T(n) 的同數量級函數,記作 T(n)=O(f(n)),被稱爲算法的漸進時間複雜度,又簡稱爲時間複雜度。- 算法(一)時間複雜度[1]

大 O 表示法

在計算機科學領域,會用大 O 符號或者說大 O 表示法來度量一個算法的時間複雜度。那什麼是大 O 表示法呢?

Big O notation is a mathematical notation that describes the limiting behavior of a function when the argument tends towards a particular value or infinity. It is a member of a family of notations invented by Paul Bachmann, Edmund Landau, and others collectively called Bachmann–Landau notation or asymptotic notation.
大 O 表示法是一種數學符號,用於描述當參數趨向於特定值或無窮大時函數的限制行爲。它是 Paul Bachmann,Edmund Landau 等人發明的一系列符號的之一,統稱爲巴赫曼 - 蘭道符號或漸近符號。- 維基百科[4]

因爲時間複雜度爲 T(n)=O(f(n)),有個趨近函數 O(),所以被稱爲大 O 表示法。現在假設一個列表包含 n 個元素。我們用簡單查找的話,需要遍歷檢查每一個元素,因此需要執行 n 次操作。使用大 O 表示法,其運行時間爲 O(n),f(n) = n 爲操作數。這裏需要注意的是,大 O 表示法是沒有單位的,它指的並不是以秒爲單位的運行時間。大 O 表示法能夠用來比較操作數,通常它指的是算法運行時間的增速。比如,爲檢查長度爲 8 的列表,用簡單查找法的時間複雜度是 O(f(n)),f(n) = 8;如果用二分查找的話,時間複雜度是 O(f(n)),f(n) = log2 8 = 3。從計算結果上看來,幾乎快了 3 倍。

還有一點需要注意的是,大 O 表示法描述的始終是最壞的情況

常見的一些大 O 運行時間有以下幾種:

  • O(1):常數時間,如哈希表
  • O(n):線性時間,如簡單查找
  • O(log2n):對數時間,如二分查找
  • O(nlog2n):如快速排序
  • O(n2):如選擇排序
  • O(n!):階乘時間,這是一種非常慢的算法

計算時間複雜度

知道了大 O 表示法,那麼實際的時間複雜度要怎麼計算呢?下面舉幾個例子來說明。

O(1):描述了一個算法不管輸入的大小是多少,其時間複雜度永遠爲常數(不變)。比如,下方的例子,判斷一個字符串 list 的首個元素是否爲空。因爲無論輸入的 list 有多長,都只判斷首字符是否爲空,執行次數爲 1,所以時間複雜度爲 O(1)。

def isFirstElementNull(str_list):
        rerurn str_list[0] == ''

O(n):描述了一個算法的時間複雜度將隨着算法輸入的增加而線性增加。比如,下面的算法,判斷一個字符串 list 是否包含某些子字符串值。雖然算法可能會很早的就停止循環,並不完全執行所有的迭代,但需記得大 O 表示法描述的是最壞的情況。如果一個算法最大迭代 n 次,那麼其時間複雜度就是 O(n)。所以,下面函數的時間複雜度爲 O(n)。

def isContainValue(str_list, value):

    for i in str_list:
        if i == value:
            return True
        else:
            return False

O(n2):描述了一種算法,其時間複雜度與輸入數據集的大小的平方成正比。這在涉及數據集的嵌套迭代的算法中很常見。更深的嵌套迭代將會有 O(n3),O(n4) 等。比如,下面的代碼是兩層迭代,按照最壞的打算,迭代總次數爲 ixj,是兩個數的相乘,可以直接表示爲 nxn,即 n 的平方。所以,時間複雜度可以表示爲 O(n2)。

def isContainDuplicates(str_list):
    for i in range(len(str_list)): # 最多迭代 i 次
        for j in range(len(str_list): # 最多迭代 j 次
            if i != j and str_list[i] = str_list[j]:
                return True
    return False

O(log2n):描述的是一種二分查找的時間複雜度。二分查找是一種算法,其輸入是一個有序的元素列表。如果要查找的元素包含在列表中,二分查找返回其位置;否則返回 null 。比如,要猜測 1-100 中的一個數字。二分查找將每次迭代的數據集減半,直到找到該值,或者直到它不再分割數據集爲止。 迭代中的數據集大小依次有 n,n/2,n/4,....,n/2k(k 爲循環的次數)。因爲最後結果 n/2k >= 1。如果令 n/2k = 1,將得到 k = log2n。所以,二分查找的時間複雜度爲 O(log2n)。

def binary_search(list, item):
    
    low = 0 
    high = len(list) - 1
    
    while low <= high :
        mid = (low + high) % 2 
        guess = list[mid]
        
        if guess == item:
            return mid
        if guess > item:
            high = mid - 1 
        else:
            low = mid + 1 
    
    return None 

參考

[1]. 算法(一)時間複雜度
[2]. 算法圖解 - Aditya Bhargava
[3]. A beginner's guide to Big O notation
[4]. Big O notation - wikipedia
[5]. Binary Search - 二分查找

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