复杂度分析学习总结
復雜度分析學習總結
1、大 O 復雜度表示法
T(n) = O( f(n) )
其中,T(n) 表示代碼執行的時間;n 表示數據規模的大小;f(n) 表示每行代碼執行的次數總和。公式中的 O,表示代碼的執行時間 T(n) 與 f(n) 表達式成正比。
大 O 時間復雜度實際上并不具體表示代碼真正的執行時間,而是表示代碼執行時間隨數據規模增長的變化趨勢,所以,也叫作漸進時間復雜度(asymptotic time complexity),簡稱時間復雜度。
int cal1(int n) {int sum = 0;int i = 1;for (; i <= n; ++i) {sum = sum + i;}return sum;}int cal2(int n) {int sum = 0;int i = 1;int j = 1;for (; i <= n; ++i) {j = 1;for (; j <= n; ++j) {sum = sum + i * j;}}}上面例子中,第一個的 T(n) = O(2n+2),第二個的 T(n) = O(2n2+2n+3),當 n 很大時,可以把它想象成 10000、100000。而公式中的低階、常量、系數三部分并不左右增長趨勢,所以都可以忽略。只需要記錄一個最大量級就可以了,如果用大 O 表示法表示剛講的那兩段代碼的時間復雜度,就可以記為:T(n) = O(n); T(n) = O(n^2)。
2、時間復雜度分析方法
(1) 只關注循環執行次數最多的一段代碼
(2) 加法法則:總復雜度等于量級最大的那段代碼的復雜度
這個規律抽象成公式就是:
如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n)))
(3)乘法法則:嵌套代碼的復雜度等于嵌套內外代碼復雜度的乘積
這個規律抽象成公式就是:
如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(f(n)*g(n))
舉個例子:
int cal(int n) {int ret = 0; int i = 1;for (; i < n; ++i) {ret = ret + f(i);} } int f(int n) {int sum = 0;int i = 1;for (; i < n; ++i) {sum = sum + i;} return sum;}如果單獨看 cal() 函數,假設 f() 只是一個普通的操作,那第 4~6 行的時間復雜度就是,T1(n) = O(n)。但 f() 函數本身不是一個簡單的操作,它的時間復雜度是 T2(n) = O(n),所以,整個 cal() 函數的時間復雜度就是,T(n) = T1(n) * T2(n) = O(n*n) =O(n^2)。
?
3、幾種常見時間復雜度實例分析
可以粗略的分為:多項式量級和非多項式量級
(1)非多項式量級
非多項式量級只有兩個:O(2n) 和 O(n!)。
我們把時間復雜度為非多項式量級的算法問題叫作 NP(Non-Deterministic Polynomial,非確定多項式)問題。當數據規模 n 越來越大時,非多項式量級算法的執行時間會急劇增加,求解問題的執行時間會無限增長。所以,非多項式時間復雜度的算法其實是非常低效的算法。
(2)多項式量級
一般常見的,從低階到高階有:O(1)、O(logn)、O(n)、O(nlogn)、O(n^2)
時間復雜度趨勢圖如下:
其中O(logn)、O(nlogn)比較特殊,如下例子可以說明
i=1;while (i <= n) {i = i * 2;} // 這段代碼的時間復雜度就是 O(log2 n)i=1; while (i <= n) { i = i * 3; } // 這段代碼的時間復雜度就是 O(log3 n)在對數階時間復雜度的表示方法里,可以忽略對數的“底”,統一表示為 O(logn)
有種情況是,如果我們無法事先評估 m 和 n 誰的量級大,所以我們在表示復雜度的時候,就不能簡單地利用加法法則,省略掉其中一個,如下例子的時間復雜度是:O(m+n)、O(m*n)
int cal(int m, int n) {int sum_1 = 0;int i = 1;for (; i < m; ++i) {sum_1 = sum_1 + i;}int sum_2 = 0;int j = 1;for (; j < n; ++j) {sum_2 = sum_2 + j;}return sum_1 + sum_2; }?
4、空間復雜度分析
定義:時間復雜度的全稱是漸進時間復雜度,表示算法的執行時間與數據規模之間的增長關系。類比一下,空間復雜度全稱就是漸進空間復雜度(asymptotic space complexity),表示算法的存儲空間與數據規模之間的增長關系
空間復雜度的定義的比較簡單,例子如下
void print(int n) {int i = 0;int[] a = new int[n];for (i; i <n; ++i) {a[i] = i * i;}for (i = n-1; i >= 0; --i) {print out a[i]} }跟時間復雜度分析一樣,可以看到,第 2 行代碼中,我們申請了一個空間存儲變量 i,但是它是常量階的,跟數據規模 n 沒有關系,所以我們可以忽略。第 3 行申請了一個大小為 n 的 int 類型數組,除此之外,剩下的代碼都沒有占用更多的空間,所以整段代碼的空間復雜度就是 O(n)。
?
5、四個復雜度分析方面知識點
最好情況時間復雜度(best case time complexity)、最壞情況時間復雜度(worst case time complexity)、平均情況時間復雜度(average case time complexity)、均攤時間復雜度(amortized time complexity)
(1)最好情況時間復雜度、最壞情況時間復雜度
顧名思義,最好情況時間復雜度就是,在最理想的情況下,執行這段代碼的時間復雜度,最壞情況時間復雜度就是,在最糟糕的情況下,執行這段代碼的時間復雜度
(2)平均情況時間復雜度、均攤時間復雜度
平均時間復雜度的全稱又叫加權平均時間復雜度或者期望時間復雜度。
// n表示數組array的長度 int find(int[] array, int n, int x) {int i = 0;int pos = -1;for (; i < n; ++i) {if (array[i] == x) {pos = i;break;}}return pos; }要查找的變量 x 在數組中的位置,有 n+1 種情況:在數組的 0~n-1 位置中和不在數組中。把每種情況下,查找需要遍歷的元素個數累加起來,然后再除以 n+1,就可以得到需要遍歷的元素個數的平均值,即:
時間復雜度的大 O 標記法中,可以省略掉系數、低階、常量,所以,咱們把剛剛這個公式簡化之后,得到的平均時間復雜度就是 O(n)。這個結論雖然是正確的,但是計算過程稍微有點兒問題。這 n+1 種情況,出現的概率并不是一樣的。要查找的變量 x,要么在數組里,要么就不在數組里。假設在數組中與不在數組中的概率都為 1/2。另外,要查找的數據出現在 0~n-1 這 n 個位置的概率也是一樣的,為 1/n。所以,根據概率乘法法則,要查找的數據出現在 0~n-1 中任意位置的概率就是 1/(2n)。因此,如果平均時間復雜度為O(n)的最大問題就是,沒有將各種情況發生的概率考慮進去。
如果把每種情況發生的概率也考慮進去,那平均時間復雜度的計算過程就變成了這樣:
這個值就是概率論中的加權平均值,也叫作期望值,所以平均時間復雜度的全稱應該叫加權平均時間復雜度或者期望時間復雜度。引入概率之后,前面那段代碼的加權平均值為 (3n+1)/4。用大 O 表示法來表示,去掉系數和常量,這段代碼的加權平均時間復雜度仍然是 O(n)。
?
均攤時間復雜度
// array表示一個長度為n的數組// 代碼中的array.length就等于nint[] array = new int[n];int count = 0;void insert(int val) {if (count == array.length) {int sum = 0;for (int i = 0; i < array.length; ++i) {sum = sum + array[i];}array[0] = sum;count = 1;}array[count] = val;++count;}在數組中插入數據的這個例子。每一次 O(n) 的插入操作,都會跟著 n-1 次 O(1) 的插入操作,所以把耗時多的那次操作均攤到接下來的 n-1 次耗時少的操作上,均攤下來,這一組連續的操作的均攤時間復雜度就是 O(1)
均攤時間復雜度就是一種特殊的平均時間復雜度,均攤時間復雜度和攤還分析應用場景比較特殊,所以并不會經常用到,在能夠應用均攤時間復雜度分析的場合,一般均攤時間復雜度就等于最好情況時間復雜度。
?
總結
- 上一篇: 处理业务代码中循环遍历出现的性能问题
- 下一篇: 递归学习总结