一 爲什麼需要複雜度分析
- 測試結果非常的依賴測試環境
在不同的硬件環境,測試同樣一份代碼其效果是不一樣的。那麼複雜度分析具有成本低,效率高的特點。 - 測試結果收到數據規模的影響很大
比如排序算法,對待排序的有序度不一樣,排序的執行時間就很不一樣。如果測試數據規模很小,測試結果也無法真實反應算法的性能。比如對於小規模的數據排序,插入排序可能比快排更快。
二. 大0複雜度表示法
算法的執行效率一般來說就是算法代碼執行的時間。那麼如何得到代碼的執行時間?先列出代碼
假設每行代碼執行時間一樣。
int calc(int n)
{
int sum=0;//執行一個time
int i=1;//執行一個time
int j=1;//執行一個time
//循環了n遍 所以是2n*time
//從此整個時間T(n) = (2n2+2n+3)*time
for(;i<=n;++i)
{
j=1;
for(;j<=n;j++)
{
sum=sum+i*j
}
}
}
從這裏出現一個重要的規律就是所有代碼的執行時間T(n)和每行代碼的執行此時n成正比Tn=O(f(n))。上面出現T(n)=2n2+2n+3,我們只需要記錄一個最大量級就行。比如T(n)=O(n2).
三. 時間複雜度分析三方法
- 只關注循環次數最多的一段代碼
大O表示法,是表示一種變化的趨勢。我們只需要關心一個最大階的量級。比如下面這個代碼
int calc(int n)
{
int sum=0;
int i=0;
for(;i<=n;++i)
{
sum=sum+i;
}
return sum;
}
這裏直接查看for部分代碼,這兩行代碼被執行了n次,所以總的時間複雜度爲O(n)
- 加法法則 總的複雜度等於量級最大的那段代碼
int calc(int n)
{
int sum=0;//執行一個time
int i=1;//執行一個time
int j=1;//執行一個time
int p = 0;
for(int p=0;p<n;p++)
{
sum=sum+p;
}
sum=0;
//循環了n遍 所以是2n*time
//從此整個時間T(n) = (2n2+2n+3)*time
for(;i<=n;++i)
{
j=1;
for(;j<=n;j++)
{
sum=sum+i*j
}
}
}
這段代碼中第一個循環p循環n次,不管這個n是100,1000等,都是產量級的時間。同理第二個第二段循環代碼爲O(n2),所以最終結果是O(n2)
- 乘法法則 嵌套代碼複雜度等於嵌套內外代碼複雜度的乘積
假設T1(n)=O(n),T2(n)=O(n),那麼T(n)=T1(n)*T2(n)=O(n2)
四 幾種常見的時間複雜度分析
- 常見複雜度有哪些
- 0(1)
int i=2;
int j=3;
int sum=i+j;
一般來說如果代碼的執行時間不隨着n的增大而增長,那麼就是O(1)。小技巧就是程序中沒有循環,遞歸等
- O(logn)和O(nlogn)
int i=1;
while(i<=n)
{
i=i*2;
}
這裏的i從1開始每次循環乘以2直到i小於了n結束。也就是20,21…..2x,那麼2x=n,x=log2n.這個時候如果i=i*4,那就是x=log4n,那麼log4n實際上等於log42*log2n,所以呢O(log4n)=O(C*log2n).之前說過我們的這個C常量,可以忽略。所以O(log4n)=O(log2n).在對數階時間複雜度表示中,忽略對數的
4 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; }
}
這裏的m和n那個量級大不好預判
五. 面試中常見算法的複雜度
- 排序相關
2. 二叉樹相關
六 總結
- 學習複雜度分析有利於我們編寫更加優秀的代碼。
- 複雜度分析的要點
(1) 單段代碼看高頻。比如看循環
(2) 多段代碼取最大。比如既有單層循環也有多層循環,取高層
(3) 嵌套代碼求乘積。比如遞歸 - 常用的複雜度
(1) 多項式階
O(1)常數階 O(logn)對數階 O(n)線性階 o(n2)平方階
(2) 非多項式
指數階 階乘階