復雜度分析
算法的復雜度指的是執行該算法的程序在運行時所需要的時間和空間(內存)資源,復雜度分析主要是從時間復雜度和空間復雜度兩個層面來考慮。
大O(big O)表示法
在了解時間復雜度之前,我們需要知道怎么用數學符號將它表示出來。
我們知道,一個算法的執行時間 = 該算法中每條語句執行時間之和。假設每條語句執行一次所需要的時間為單位時間,那么一個算法的執行時間就和算法中需要執行語句的次數成正比,即是等於所有語句的頻度(執行次數)之和。
用T[n]表示代碼的執行時間,n表示數據規模的大小,f(n)表示每行代碼執行的次數總和,算法執行時間的公式為:
$$
T[n] = O(f(n))
$$
O表示的是代碼執行時間隨數據規模增長的變化趨勢,也叫做漸進時間復雜度(asymptotic time complexity),簡稱時間復雜度. 下面我們看一個具體的例子:
public int GetSum(int n)
{
int sum = 0;
int i = 0;
int j = 0;
for(; i < n; i++)
{
sum += i;
for(; j < n; j++)
{
sum = sum + i * j;
}
}
return sum;
}
在上面例子中,由於已經假設每條語句執行一次所需時間為單位時間,第3,4,5行執了一次,第6,8行分別執行了n次,第9,11行分別執行了n^2次,可以得出
$$
T(n) = O(2n^2 + 2n + 3)
$$
當n的值非常大時,比如n=100000或者更大時,公式中的低階,常數和系數三部分並不左右增長趨勢,因此可以忽略不計,簡單點,我們可以將公式表示為:
$$
T(n) = O(n^2)
$$
很多時候,在求一個算法的時間復雜度時,我們都會將n看作一個很大的數,因此只要它的高階項,不要低階項,也不要高階的系數,在后面的例子中還會有所體現.
時間復雜度分析
前面我們已經知道了big O表示法的由來以及如何表示,下面我們具體講解如何計算一段代碼的時間復雜度.我們只需要記住它的一條原則:只要高階項,不要低階項,也不要高階項的系數.
為什么可以這么說呢?因為前面已經說過了,big O表示法只是表示一種變化趨勢,當執行次數n變得無窮大時,整個變化趨勢基本上是由高階項所決定的,低階項對它的影響微乎其微.看下面幾個例子
public int cal(int n)
{
int sum_1 = 0;
int p = 1;
for (; p < 100; ++p)
{
sum_1 = sum_1 + p;
}
int sum_2 = 0;
int q = 1;
for (; q < n; ++q)
{
sum_2 = sum_2 + q;
}
int sum_3 = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i)
{
j = 1;
for (; j <= n; ++j)
{
sum_3 = sum_3 + i * j;
}
}
return sum_1 + sum_2 + sum_3;
}
根據上面的法則,我們可以輕易的得出,它的時間復雜度為 O(n^2)
再看一個例子:
public int cal(int n)
{
int ret = 0;
int i = 1;
for (; i < n; ++i)
{
ret = ret + f(i);
}
}
private int f(int n)
{
int sum = 0;
int i = 1;
for (; i < n; ++i)
{
sum = sum + i;
}
return sum;
}
函數cal的時間復雜度是多少呢? 第一個for循環里面嵌套了一個函數,嵌套的函數中又有一個for循環,因此時間復雜度為O(n^2)
常見時間復雜度分析
下面是常見的幾種復雜度,簡單解釋其中的幾種

- O(1) : 表示常量級的時間復雜度,並不是說只執行了一條語句,只要執行語句的數量是常量,都可以用它來表示
- O(logn) : 對數級,在分析復雜度時,不管對數的底是多少,根據數學公式,都可以將其化成底為2的對數,而在big O表示法中,我們忽略系數,所以在對數階時間復雜度的表示方法中,統一為O(logn)
時間復雜度的四種類型
大部分代碼的復雜度分析按照上述法則分析都足以應付,但對於少部分代碼,它們的時間復雜度會隨着輸入數據的順序,位置不同而存在量級的差距.在這種情況下,我們才需要使用到最好時間復雜度,最壞時間復雜度,平均時間復雜度,均攤時間復雜度去分析這部分代碼.
看這個例子,思考一下它的時間復雜度該怎么表示呢?
public int find(int[] array, int x)
{
int i = 0;
int pos = -1;
for (; i < array.Length; ++i)
{
if (array[i] == x)
{
pos = i;
break;
}
}
return pos;
}
代碼很簡單,遍歷數組array,查看是否存在值為x的數字,如果有,返回其下標,否則返回-1.
如果數組中第一個元素的值等於x,則它的時間復雜度是O(1),如果數組中不存在值等於x的元素,則它的時間復雜度為O(n),也就是說,在不同情況下,它的復雜度不一樣,因此我們需要分情況進行討論
最好時間復雜度
指的是在理想情況(最好情況)下,執行這段代碼的時間復雜度,上面例子中,它的最好時間復雜度為O(1).
最壞時間復雜度
指的是在最壞情況下,執行這段代碼的時間復雜度,上面例子中,它的最壞時間復雜度為O(n).
平均時間復雜度
指的是概率論中的加權平均值,也叫作期望值,所以平均時間復雜度的全稱應該叫加權平均時間復雜度或者期望時間復雜度,它的公式如下。

其中A(n)表示平均時間復雜度,S是規模為n的實例集,實例I∈S的概率為PI,算法對實例I執行的基本運算次數是tI。
上面排序算法中,有 n+1 種情況(在數組中有 n 種情況,不在數組中有 1 種情況),我們分別需要查找
次。假設每種情況出現的概率都一樣,那所有的查找次數平均下來即為
,加權平均和為
,根據我們前面時間復雜度的加法原則,我們去掉低階項,去掉系數以后這種情況最終時間復雜度的大小為
。
上面的例子結論雖然是正確的,由於 n + 1 種情況出現的概率不一樣,因此並不能按照上面的方式進行計算。首先我們知道,要查找一個數,這個數要么在數組中,要么不在數組中,為了方便理解,我們假設它們的概率都是1/2,如果在數組中,被遍歷到的概率是1/n (因為有n個位置,每個位置出現的概率都相同), 所以數據被查找到的概率是1/2 * 1/n即1/2n,所以它的平均復雜度的計算過程為:

均攤時間復雜度
均攤時間復雜度(amortized time complexity),它對應的分析方法為攤還分析或者平攤分析。
聽起來與平均時間復雜度有點類似,比較容易弄混,平均復雜度只在某些特殊情況下才會用到,而均攤時間復雜度應用的場景比它更加特殊、更加有限。
對一個數據結構進行一組連續操作中,大部分情況下時間復雜度都很低,只有個別情況下時間復雜度比較高,而且這些操作之間存在前后連貫的時序關系,這個時候,我們就可以將這一組操作放在一塊兒分析,看是否能將較高時間復雜度那次操作的耗時,平攤到其他那些時間復雜度比較低的操作上。而且,在能夠應用均攤時間復雜度分析的場合,一般均攤時間復雜度就等於最好情況時間復雜度。
空間復雜度分析
時間復雜度的全稱是漸進時間復雜度,表示算法的執行時間與數據規模之間的增長關系。類比一下,空間復雜度全稱就是漸進空間復雜度(asymptotic space complexity),表示算法的存儲空間與數據規模之間的增長關系。它的分析規則與時間復雜度一樣,也是只要高階項,不要低階項,也不要高階項的系數,看下面的例子:
public void print(int n)
{
int i = 0;
int[] a = new int[n];
for (; i < n; ++i)
{
a[i] = i * i;
}
for (i = n - 1; i >= 0; --i)
{
Console.WriteLine(a[i]);
}
}
顯而易見,其忽略低階項和常數項,其空間復雜度為 O(n)
總結

