AMD 入門級顯示卡 RX 6400/6500 XT 引數曝光,明年 Q1 推出
寫在前面
在學習資料結構和演算法的時候,經常會碰到O(1),O(n)等等用來表示時間和空間複雜度,那這到底是什麼意思。我們對於同一個問題經常有不同的解決方式,比如排序演算法就有十種經典排序(快排,歸併排序等),雖然對於排序的結果相同,但是在排序過程中消耗時間和資源卻是不同。
對於不同排序演算法之間的衡量方式就是通過程式執行所佔用的時間和空間兩個維度去考量。
高中數學
函式
設A、B是非空的數集,如果按照某個確定的對應關係f,使對於集合A中的任意一個數x,在集合B中都有唯一確定的數f(x)和它對應,那麼就稱f:A→B為從集合A到集合B的一個函式。記作:y=f(x),x∈A。其中,x叫做自變數,x
例:已知f(x)的定義域為[3,5],求f(2x-1)的定義域。
冪函式
$$
y=x^k
$$
指數函式
$$
函式y=a^x (a>0且a\neq1)叫做指數函式,自變數叫做指數,a叫做底數。
$$
對數函式
$$
如果a(a>0,a\neq1)的b次冪等於N,即a^b=N,那麼b叫做以a為底N的對數,記作log_aN=b,其中a叫做對數的底數,N叫做真數
$$
時間複雜度
若存在函式 f(n),使得當n趨近於無窮大時,T(n)/ f(n))的極限值為不等於零的常數,則稱 f(n)是T(n)的同數量級函式。記作 T(n)= O(f(n)),稱O(f(n))為演算法的漸進時間複雜度,簡稱時間複雜度。
簡單理解就是一個演算法或是一個程式在執行時,所消耗的時間(或者程式碼被執行的總次數)。
在下面的程式中:
int sum(int n) {
① int value = 0;
② int i = 1;
③ while (i <= n) {
④ value = value + i;
⑤ i++;
}
⑥ return value;
}
假設n=100,該方法的執行次數為①(1次)、②(1次)、③(100次)、④(100次)、⑤(100次)、⑥(1次)
合計1+1+100+100+100+1 = 303次
上面的結果如果用函式來表示為:f(n) = 3n+3,那麼在計算機演算法中的表示方法如下。
表示方法
大O表示法:演算法的時間複雜度通常用大O來表示,定義為T(n) = O(f(n)),其中T表示時間。
即:T(n) = O(3n+3)
這裡有個重要的點就是時間複雜度關心的是數量級,其原則是:
- 省略常數,如果執行時間是常數量級,用常數1表示
- 保留最高階的項
- 變最高階項的係數為1
如 2n 3 + 3n2 + 7,省略常數變為 O(2n 3 + 3n2),保留最高階的項為 O(2n 3 ),變最高階項的係數為1後變為O(n 3 ),即O(n 3 )為 2n 3 + 3n2 + 7的時間複雜度。
同理,在上面的程式中 T(n) = O(3n+3),其時間複雜度為O(n)。
注:只看最高複雜度的運算,也就是上面程式中的內層迴圈。
時間複雜度的階
時間複雜度的階主要分為以下幾種
常數階O(1)
int n = 100;
System.out.println("常數階:" + n);
不管n等於多少,程式始終只會執行一次,即 T(n) = O(1)
對數階O(logn)
// n = 32 則 i=1,2,4,8,16,32
for (int i = 1; i <= n; i = i * 2) {
System.out.println("對數階:" + n);
}
i 的值隨著 n 成對數增長,讀作2為底n的對數,即f(x) = log2n,T(n) = O( log2n),簡寫為O(logn)
則對數底數大於1的象限通用表示為:
線性階O(n)
for (int i = 1; i < n; i++) {
System.out.println("線性階:" + n);
}
n的值為多少,程式就執行多少次,類似函式 y = f(x),即 T(n) = O(n)
線性對數階O(nlogn)
for (int m = 1; m <= n; m++) {
int i = 1;
while (i < n) {
i = i * 2;
System.out.println("線性對數階:" + i);
}
}
線性對數階O(nlogn)其實非常容易理解,將對數階O(logn)的程式碼迴圈n遍的話,那麼它的時間複雜度就是 n * O(logn),也就是了O(nlogn),歸併排序的複雜度就是O(nlogn)。
若n = 2 則程式執行2次,若n=4,則程式執行8次,依次類推
平方階O(n2)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
System.out.println("平方階:" + n);
}
}
若 n = 2,則列印4次,若 n = 3,則列印9,即T(n) = O(n2)
同理,立方階就為O(n3),如果3改為k,那就是k次方階O(nk),相對而言就更復雜了。
以上5種時間複雜度關係為:
從上圖可以得出結論,當x軸n的值越來越大時,y軸耗時的時長為:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2)
在程式設計演算法中遠遠不止上面4種,比如O(n3),O(2n),O(n!),O(nk)等等。
這些是怎麼在數學的角度去證明的,感興趣的可以去看看主定理。
注:以下資料來自於Big-O Cheat Sheet,常用的大O標記法列表以及它們與不同大小輸入資料的效能比較。
大O標記法 | 計算10個元素 | 計算100個元素 | 計算1000個元素 |
---|---|---|---|
O(1) | 1 | 1 | 1 |
O(logN) | 3 | 6 | 9 |
O(N) | 10 | 100 | 1000 |
O(NlogN) | 30 | 600 | 9000 |
O(N2) | 100 | 10000 | 1000000 |
O(2N) | 1024 | 1.26e+29 | 1.07e+301 |
O(N!) | 3628800 | 9.3e+157 | 4.02e+2567 |
常見資料結構操作的複雜度
資料結構 | 連線 | 查詢 | 插入 | 刪除 |
---|---|---|---|---|
陣列 | 1 | n | n | n |
棧 | n | n | 1 | 1 |
佇列 | n | n | 1 | 1 |
連結串列 | n | n | 1 | 1 |
雜湊表 | - | n | n | n |
二分查詢樹 | n | n | n | n |
B樹 | log(n) | log(n) | log(n) | log(n) |
紅黑樹 | log(n) | log(n) | log(n) | log(n) |
AVL樹 | log(n) | log(n) | log(n) | log(n) |
陣列排序演算法的複雜度
名稱 | 最優 | 平均 | 最壞 | 記憶體 | 穩定 |
---|---|---|---|---|---|
氣泡排序 | n | n2 | n2 | 1 | 是 |
插入排序 | n | n2 | n2 | 1 | 是 |
選擇排序 | n2 | n2 | n2 | 1 | 否 |
堆排序 | n log(n) | n log(n) | n log(n) | 1 | 否 |
歸併排序 | n log(n) | n log(n) | n log(n) | n | 是 |
快速排序 | n log(n) | n log(n) | n2 | log(n) | 否 |
希爾排序 | n log(n) | 取決於差距序列 | n (log(n))2 | 1 | 否 |
空間複雜度
空間複雜度表示的是演算法的儲存空間和資料之間的關係,即一個演算法在執行時,所消耗的空間。
空間複雜度的階
空間複雜度相對於時間複雜度要簡單很多,我們只需要掌握常見的O(1),O(n),O(n2)。
常數階O(1)
int i;
線性階O(n)
int[] arr;
平方階O(n2)
int[][] arr;
總結
寫這篇文章的目的在於,我在更新棧和佇列的時候,留下了一個問題棧的入棧和出棧操作與佇列的插入和移除的時間複雜度是否相同,確實是相同的 ,都用了O(1)。由此我想到,對於剛開始或者說是不太瞭解複雜度的同學,碰到此類的問題,或者是我在跟後續資料結構和演算法文章的時候不懂,十分的不友好,於是就寫了一篇關於複雜 度的文章。
本文只對時間複雜度和空間複雜度做了簡單介紹,有錯誤可以指正,不要硬槓,槓就是我輸。