数据结构与算法 -- 时间复杂度
數(shù)據(jù)結(jié)構(gòu)與算法看完了,回過頭來在看時(shí)間復(fù)雜度,對(duì)它們的效率做個(gè)比較吧。
一、時(shí)間復(fù)雜度介紹
1、時(shí)間復(fù)雜度定義
參看:數(shù)據(jù)結(jié)構(gòu)-算法-時(shí)間復(fù)雜度計(jì)算
在進(jìn)行算法分析,語句總得執(zhí)行次數(shù) T(n) 是關(guān)于問題規(guī)模 n 的函數(shù),進(jìn)而分析 T(n) 隨 n 的變化情況并確定 T(n) 數(shù)量級(jí)。算法的時(shí)間復(fù)雜度,也就是算法的時(shí)間量度,記作:T(n) = O(f(n)),它表示隨問題規(guī)模 n 的增大算法執(zhí)行時(shí)間的增長(zhǎng)率和 f(n) 的增長(zhǎng)率相同,稱作算法的漸近時(shí)間復(fù)雜度,簡(jiǎn)稱為時(shí)間復(fù)雜度,其中 f(n) 是問題規(guī)模 n 的某個(gè)函數(shù)。
2、求解時(shí)間復(fù)雜度具體步驟
(1)找出算法中的基本語句 算法中執(zhí)行次數(shù)最多的那條語句就是基本語句,通常是最內(nèi)層循環(huán)的循環(huán)體。 (2)計(jì)算基本語句的執(zhí)行次數(shù)的數(shù)量級(jí) 只需計(jì)算基本語句執(zhí)行次數(shù)的數(shù)量級(jí),這就意味著只要保證基本語句執(zhí)行次數(shù)的函數(shù)中的最高次冪正確即可。可以忽略所有低次冪和最高次冪的系數(shù)。這樣能夠簡(jiǎn)化算法分析,并且使注意力集中在最重要的一點(diǎn)上:增長(zhǎng)率。 (3)用大 O 記號(hào)表示算法的時(shí)間性能 將基本語句執(zhí)行次數(shù)的數(shù)量級(jí)放入大 O 記號(hào)中。 大 O 中的 O 的意思就是"order of"(大約是),它是種概念,就比如 大型車、小型車和中型車,忽略具體大小尺寸,來描述汽車。3、時(shí)間復(fù)雜度計(jì)算方法
(1)用常數(shù) 1 取代運(yùn)行時(shí)間中的所有加法常數(shù)。 (2)在修改后的運(yùn)行次數(shù)函數(shù)中,只保留最高階項(xiàng)。 (3)如果最高階存在且不是 1,則去除與這個(gè)項(xiàng)相乘的常數(shù)。最后,得到的最后結(jié)果就是時(shí)間復(fù)雜度。 簡(jiǎn)單來說,就是保留求出次數(shù)的最高次冪,并且把系數(shù)去掉,如 T(n) = 2n^2+n+1 = O(n^2)4、常見的時(shí)間復(fù)雜度
(1)常數(shù)級(jí)復(fù)雜度:O(1)(2)對(duì)數(shù)級(jí)復(fù)雜度:O(logN)
(3)線性級(jí)復(fù)雜度:O(N)
(4)線性對(duì)數(shù)級(jí)復(fù)雜度:O(NlogN)
(5)平方級(jí)復(fù)雜度:O(N^2)
復(fù)雜度曲線越平越好,越陡越差,常數(shù)級(jí)復(fù)雜度最為理想。 常用的時(shí)間復(fù)雜度所耗費(fèi)的時(shí)間從小到大依次是: O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
舉例說明:
執(zhí)行次數(shù)函數(shù) | 階 | 非正式術(shù)語 |
12 | O(1) | 常數(shù)階 |
5log2^n + 20 | O(logn) | 對(duì)數(shù)階 |
2n + 3 | O(n) | 線性階 |
2n + 3nlog2^2 + 19 | O(nlogn) | 線性對(duì)數(shù)階 |
3n^2 + 2n + 1 | O(n^2) ? ? | 平方階 |
6n^3 + 2n^2 + 3n + 4 ? ? | O(n^3) | 立方階 |
2^n | O(2^n) | 指數(shù)階 |
二、具體說明各種時(shí)間復(fù)雜度
按照時(shí)間復(fù)雜度從小到大依次講解:
講解之前需要了解一個(gè)概念:頻度
參看:數(shù)據(jù)結(jié)構(gòu)頻度
在數(shù)據(jù)結(jié)構(gòu)中,頻度是指一個(gè)定義變量在它的函數(shù)中,并且是它在執(zhí)行到該段語句為止時(shí),這個(gè)定義變量在函數(shù)總共執(zhí)行基本操作的次數(shù)。
例如下函數(shù)中各行頻度n的計(jì)算:
for(i=0;i<n;i++) ----------------------------- (1)
{for(j=0;j<n;j++) ------------------------- (2){c[i][j]=0; ------------------------------ (3)for(k=0;k<n;k++) ------------------- (4){c[i][j]=c[i][j]+a[i][k]*b[k][j]; ------- (5)}}
}(1) for(i=0;i<n;i++) ?頻度為: n+1(2) for(j=0;j<n;j++) ?頻度為:n*(n+1)
(3) c[i][j]=0 ?頻度為:n*n
(4) for(k=0;k<n;k++) ?頻度為:n*n*(n+1)
(5) c[i][j]=c[i][j]+a[i][k]*b[k][j] ?頻度為:n*n*n
解釋: (1)i 變量在第一個(gè) for 循環(huán)中,從取 i = 0 開始執(zhí)行,直到i=n-1時(shí)為止,至此,i 執(zhí)行了n次。加上最后i=n跳出循環(huán)的判斷,故頻度共n+1 次;
(2)與(1)不同,當(dāng) i 在 0~(n-1) 范圍內(nèi),內(nèi)層循環(huán) [即是(2)的for循環(huán)] 頻度為 n ; 當(dāng) i = n 時(shí),內(nèi)層循環(huán)語句沒執(zhí)行。所以相當(dāng)此時(shí)第(1)中 for 循環(huán)執(zhí)行了n次,第二個(gè)for 循環(huán)執(zhí)行了n次,加上最后j=n跳出循環(huán)的判斷,即,頻度共 n * (n+1);
(3)此句語句,是要利用(1)、(2)for循環(huán)語句的i ,j 對(duì) c[i][j] 進(jìn)行賦值,此時(shí),i 得到的賦值只有從 0 到 n , j 得到的賦值也是從0到n ,都是 n次,此時(shí)(當(dāng) i 達(dá)到n-1 .\當(dāng) j 達(dá)到 n-1.)的 i++ \j++都不會(huì)執(zhí)行。 故,頻度共 n*n 次;
(4)同上(1),(2)的理由,單獨(dú)的(4)的for 循環(huán)執(zhí)行了n+1 次,綜上,頻度為 n*n*(n+1);
(5)同理(3),對(duì)于三個(gè)for 循環(huán), i 得到的賦值只有從 0 到 n , j 得到的賦值也是從0到n ,k得到的賦值也是從 0 到 n ,即,頻度為n*n*n。
1、常數(shù)階(O(1))
#include <stdio.h>int main (void)
{int n = 10;printf ("n = %d\n", n);printf ("n = %d\n", n);printf ("n = %d\n", n);printf ("n = %d\n", n);printf ("n = %d\n", n);printf ("n = %d\n", n);printf ("n = %d\n", n);printf ("n = %d\n", n);return 0;
}
分析:
上述代碼中,每條語句的頻度均為 1,即 T(n) = O(9),但是一般記作 O(1)。因此它的時(shí)間復(fù)雜度為 O(1)
(2)對(duì)數(shù)階(O(logn))
#include <stdio.h>int main (void) {int i = 1, n = 1000; //語句1while (i < n) i = i*2; //語句2return 0; } 分析:語句1頻度為:1語句2頻度為:f(n),則2f(n)<=n;f(n)<=log2n ?取最大值 f(n)= log2n即T(n) =?log2n + 1 =?O(log2n ),因此它的時(shí)間復(fù)雜度為 O(logn)(3)線性階(O(n))
#include <stdio.h>int main (void)
{int i = 0, sum = 0, n = 10; //語句1for (i = 0; i < n; i++) //語句2sum += i; //語句3printf ("sum = %d\n", sum); //語句4 return 0;
}
輸出結(jié)果:
sum = 45
分析:語句1頻度為:1語句2頻度為:n+1語句3頻度為:n語句4頻度為:1即T(n) = 1 + (n + 1) + n + 1 = 2n + 3 = O(n),因此它的時(shí)間復(fù)雜度為O(n)
(4)線性對(duì)數(shù)階(O(nlongn))
#include <stdio.h> void quick (int arr[], int left, int right) { int p = (left + right) / 2; int pivot = arr[p]; int i = 0, j = 0; for(i = left, j = right; i < j;) { while (arr[i] < pivot && i < p) i++; if (i < p) { arr[p] = arr[i]; p = i;} while(arr[j] >= pivot && j > p) j--; if (j > p) { arr[p] = arr[j]; p = j; } arr[p] = pivot; if(p - left > 1) quick (arr, left, p - 1); if (right - p > 1) quick (arr, p + 1, right); } } int main() { int arr[9] = {20, 8, 25, 3, 15, 9, 30, 5, 22}; quick (arr, 0, 8); int i = 0; for(i = 0; i < 9; i++) printf ("%d ", arr[i]); printf ("\n"); return 0; } 輸出結(jié)果: 3 5 8 9 15 20 22 25 30 分析:上述例子為快速排序,如果每次都是均等的劃分即T(n)=T(n/2)+T(n/2)+O(n),即每次分成兩段,則分的次數(shù)為logn,每一次處理需要n次計(jì)算,那么時(shí)間復(fù)雜度就是nlogn如果每次的劃分都是完全不平衡的即T(n)=T(n-1)+O(n),那么快排的時(shí)間復(fù)雜度是n^2所以它是不穩(wěn)定的,因此我們說?快排的平均時(shí)間復(fù)雜度是nlogn(5)平方階(O(n^2))
#include <stdio.h> int main (void) { int i, j, n = 10; //語句1for (i = 0;i < n; i++) //語句2{for (j = 0; j < n; j++) //語句3printf ("*"); printf ("\n"); }return 0; } 分析:語句1頻度為:1語句2頻度為:n + 1語句3頻度為 n * (n + 1)即T(n) = 1 + (n + 1) + n * (n + 1) = n^2 + 2n + 2 = O(n^2),因此它的時(shí)間復(fù)雜度為 O(N^2)(6)立方階(O(n^3))
#include <stdio.h> int main (void) { int i, j, k, n = 3; //語句1for (i = 0;i < n; i++) //語句2{for (j = 0; j < n; j++) //語句3{for (k = 0; k < n; k++) //語句4printf ("*");printf ("\n");}}return 0; } 分析: 語句1頻度為:1 語句2頻度為:n + 1 語句3頻度為:n * (n + 1) 語句4頻度為:n * n * (n + 1) 即T(n) = 1 + (n + 1) + n * (n + 1) + n * n * (n + 1) = n^3 + 2n^2 + 2n + 2 = O(n^3) 因此它的時(shí)間復(fù)雜度為 O(n^3)(7)指數(shù)階(O(2^n))
#include <stdio.h> int i = 1;void move (int n, char from, char to) { printf ("第%d步:將%d號(hào)盤子%c---->%c\n", i++, n, from, to); //語句1 } void hanoi (int n, char from, char denpend_on, char to) { if (n == 1) move (1, from, to); else { hanoi (n - 1, from, to, denpend_on); move (n, from, to); hanoi (n - 1, denpend_on, from, to); } } int main (void) { printf ("請(qǐng)輸入盤子的個(gè)數(shù):\n"); int n; scanf ("%d", &n); char x = 'A', y = 'B', z = 'C'; printf ("盤子移動(dòng)情況如下:\n"); hanoi (n, x, y, z); } 分析:語句1頻度為:1?因此,當(dāng) if (n == 1) 時(shí), T(n) = 1 = O(1)否則,T(n) = 2T(n - 1) + 1 = 2^n + 1 = O(2^n) ?因此它的時(shí)間復(fù)雜度為 O(2^n)相關(guān)遞推公式就不寫了,寫了也看不懂。三、常用數(shù)據(jù)結(jié)構(gòu)和算法的時(shí)間復(fù)雜度
參看:常用數(shù)據(jù)結(jié)構(gòu)和算法操作效率的對(duì)比總結(jié)1、數(shù)據(jù)結(jié)構(gòu)部分
數(shù)據(jù)結(jié)構(gòu)中常用的操作的效率表
通用數(shù)據(jù)結(jié)構(gòu) | 查找? | 插入? | ?刪除 | 遍歷? |
數(shù)組 | O(N) | O(N) | O(N) | — |
有序數(shù)組 | O(logN) | O(N) | O(N) | O(N) |
鏈表 | O(N) | O(1) | O(N) | — |
有序鏈表 | O(N) | O(N) | O(N) | O(N) |
二叉樹 | O(logN) | O(logN) | O(logN) | O(N) |
二叉樹(最壞) | O(N) | O(N) | O(N) | O(N) |
紅黑樹 | O(logN) | O(logN) | O(logN) | O(N) |
2-3-4樹 | O(logN) | O(logN) | O(logN) | O(N) |
哈希表 | O(1) | O(1) | O(1) | — |
專用數(shù)據(jù)結(jié)構(gòu) | ? | ? | ? | ? |
棧 | — | O(1) | O(1) | — |
隊(duì)列 | — | O(1) | O(1) | — |
優(yōu)先級(jí)隊(duì)列 | — | O(N) | O(1) | — |
優(yōu)先級(jí)隊(duì)列(堆) | — | O(logN) | O(logN) | ? |
2、排序部分
常見的排序算法比較表
排序 | 平均情況 | 最好情況 | 最壞情況 | 穩(wěn)定與否 | 空間復(fù)雜度 |
冒泡排序 | O(N2) | O(N) | O(N2) | 穩(wěn)定 | 1 |
選擇排序 | O(N2) | O(N2) | O(N2) | 不穩(wěn)定 | 1 |
插入排序 | O(N2) | O(N) | O(N2) | 穩(wěn)定 | 1 |
希爾排序 | O(NlogN) | (依賴于增量序列) | 不穩(wěn)定 | 1 | |
快速排序 | O(NlogN) | O(NlogN) | O(N2) | 不穩(wěn)定 | O(logN) |
歸并排序 | O(NlogN) | O(NlogN) | O(NlogN) | 穩(wěn)定 | O(N) |
二叉樹排序 | O(NlogN) | O(NlogN) | O(N2) | 穩(wěn)定 | O(N) |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | 不穩(wěn)定 | 1 |
拓?fù)渑判?/span> | O(N+E) | — | — | — | O(N) |
3、平均和最壞時(shí)間復(fù)雜度
(1)最壞時(shí)間復(fù)雜度 顧名思義,時(shí)間復(fù)雜度不能再壞了。 以快排為例,在最壞情況下的時(shí)間復(fù)雜度為T(n) = O(n^2),它表示對(duì)于任何輸入實(shí)例,該算法的運(yùn)行時(shí)間不可能大于0(n^2) (2)平均時(shí)間復(fù)雜度 平均時(shí)間復(fù)雜度是指所有可能的輸入實(shí)例均以等概率出現(xiàn)的情況下。還以快排為例,如果每次均等劃分,如果每次都是均等的劃分即T(n)=T(n/2)+T(n/2)+O(n),即每次分成兩段,則分的次數(shù)為logn,每一次處理需要n次計(jì)算,那么時(shí)間復(fù)雜度就是nlogn。如果每次的劃分都是完全不平衡的即T(n)=T(n-1)+O(n),那么快排的時(shí)間復(fù)雜度是n^2。但它是不穩(wěn)定的,無法確保它是否均等劃分,因此我們說快排的平均時(shí)間復(fù)雜度是nlogn。
四、時(shí)間復(fù)雜度和實(shí)際運(yùn)行時(shí)間
時(shí)間復(fù)雜度和實(shí)際運(yùn)行時(shí)間不是一碼事,我們?cè)谟?jì)算時(shí)間復(fù)雜度的時(shí)候,是忽略所有低次冪和最高次冪的系數(shù)的。 比如有一個(gè)算法,輸入n個(gè)數(shù)據(jù),經(jīng)過3n^2+log2(n)+n+5次計(jì)算得到結(jié)果,其時(shí)間復(fù)雜度為O(n^2)。另外一個(gè)算法只需2n^2次計(jì)算就能得到結(jié)果,其時(shí)間復(fù)雜度也是O(n^2),但明顯比第一個(gè)算法要快。 所以說時(shí)間復(fù)雜度是可以推演計(jì)算的,而實(shí)際運(yùn)算時(shí)間不可預(yù)測(cè)。這也是為什么使用時(shí)間復(fù)雜度而不是使用實(shí)際運(yùn)算時(shí)間來判斷一個(gè)算法的優(yōu)劣了。總結(jié)
以上是生活随笔為你收集整理的数据结构与算法 -- 时间复杂度的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大话业务流程图(二)—如何绘制业务流程图
- 下一篇: 日常生活 -- 数据结构与算法告一段落