程序猿必修課之數據結構(二)算法和算法的複雜度

算法

算法是解決特定問題求解步驟的描述,在計算機中表現爲指令的有限序列,並且每條指令表示一個或多個操作。

算法的特性

算法具有五個基本特性:輸入、輸出、有窮性、確定性、可行性。

算法設計的要求

好的算法,應該具有:正確性、可讀性、健壯性、高效率和低存儲量的特徵。

函數的漸近增長

輸入規模 n 在沒有限制的情況下,只要超過一個數值 N, 這個函數就總是大於另一個函數,我們稱函數是漸近增長的。

給定兩個函數 f(n) 和 g(n), 如果存在一個整數 N,使得對於所有的 n > N, f(n) 總是比 g(n) 大,那麼我們說 f(n) 的增長漸近快於 g(n)。

算法時間複雜度

在進行算法分析時,語句總的執行次數 T(n) 是關於問題規模 n 的函數,進而分析 T(n) 隨 n 的變化情況並確定 T(n) 的數量級。算法的時間複雜度,也就是算法的時間量度,記作:T(n) = O(f(n))。它表示隨問題規模 n 的增大,算法執行時間的增長率和 f(n) 的增長率相同,稱爲算法的漸近時間複雜度,簡稱爲時間複雜度。其中 f(n) 是規模 n 的某個函數。

用 O() 來體現算法時間複雜度的記法,叫作大 O 記法。

推導大 O 階方法

  1. 用常數 1 取代運行時間中的所有加法常數。
  2. 在修改後的運行次數函數中,只保留最高階項。
  3. 如果最高階項存在且不是 1 ,則去除與這個項相乘的常數。

常數階

高斯算法

int sum = 0, n = 100;   // 執行 1 次
sum = (1 + n) * n / 2;  // 執行 1 次
printf("%d", sum);      // 執行 1 次

這個算法的運行次數函數是 f(n) = 3。根據推導大 O 階的方法,第一步把常數項 3 改爲 1。第二步保留最高階項,它沒有最高階項,所以這個算法的時間複雜度爲 T(n) = O(1)。

當 n = 1 時,算法執行次數爲 3, 當 n = 100時,算法的執行次數還是 3,所以我們可以看出這個算法的執行次數與 n 的規模沒關係。我們把這種與問題的大小(n 的大小)無關,執行時間恆定的算法, 叫作常數階。

對於分支結構,無論是真還是假,執行的次數都是恆定的,不會隨着 n 的變化而變化,所以單純的分支結構(不包含在循環結構中),其時間複雜度也是 O(1)。

線性階

下面這段代碼的時間複雜度爲 O(n),因爲循環體中的代碼必須要執行 n 次。

int i;
for (i = 0; i < n; i++) {
    /* 時間複雜度爲 O(1) 的程序步驟序列 */
}

對數階

int count = 1;
while (count < n) {
    count = count * 2;
    /* 時間複雜度爲 O(1) 的程序步驟序列 */
}

由於每次 count 乘以 2 以後,就越來越接近於 n,也就是說有多少個 2 相乘後大於 n,則會退出循環。由 2x = n 等到 x = log2n。所以這個算法的時間複雜度爲 T(n) = O(logn)。

平方階

這是一個循環嵌套的代碼。

int i, j;
for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++) {
        /* 時間複雜度爲 O(1) 的程序步驟序列 */
    }
}

它的內循環我們已經知道,時間複雜度爲 O(n),而對於外層的循環,不過是內部這個時間複雜度爲 O(n) 的語句,再循環 n 次。所以這段代碼的時間複雜度爲 O(n2)

如果外循環的次數改爲了 m,時間複雜度就變爲 O(m*n)。

int i, j, m;
for (i = 0; i < m; i++) {
    for (j = 0; j < n; j++) {
        /* 時間複雜度爲 O(1) 的程序步驟序列 */
    }
}

所以我們可以總結得出,循環的時間複雜度等於循環體的複雜度乘以該循環運行的次數。

下面這個循環嵌套,它的時間複雜度是多少呢?

int i, j;
for (i = 0; i < n; i++) {
    for (j = i; j < n; j++) {   // 注意 j = i 而不是 0
        /* 時間複雜度爲 O(1) 的程序步驟序列 */
    }
}

由於當 i = 0 時,內循環執行了 n 次,當 i = 1 時,執行了 n -1 次,…… 當 i = n - 1 時,執行了 1 次。所以部的執行次數爲:

n + (n-1) + (n-2) + …… + 1 = n(n+1)/2 = n2/2 + n/2。

用我們推導大 O 階的方法,第一條,沒有加法常數不考慮;第二條,只保留最高階項,因此保留 n2/2;第三條,去除這個項相乘的常數,也就是去除 1/2, 最終這個算法的時間複雜度爲 T(n) = O(n2)

常見的時間複雜度

非正式術語
O(1) 常數階
O(n) 線性階
O(n2) 平方階
O(logn) 對數階
O(nlogn) nlogn階
O(n3) 立方階
O(2n) 指數階

常用的時間複雜度所耗費的時間從小到大依次是:

O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)

算法空間複雜度

算法的空間複雜度通過計算算法所需的存儲空間實現,算法空間複雜度的計算公式:S(n) = O(f(n)),其中 n 爲問題的規模,f(n)爲語句關於 n 所佔存儲空間的函數。

總結

  1. 算法是解決特定問題求解步驟的描述,在計算機中爲指令的有限序列,並且每條指令表示一個或多個操作。
  2. 算法的特性:有窮性、確定性、可行性、輸入、輸出。
  3. 算法的設計要求:正確性、可讀性、健壯性、高效率和低存儲量需求。
  4.  



作者:Xiao_Mai
鏈接:https://www.jianshu.com/p/d72d4c9e90c6
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

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