【数据结构与算法-java实现】一 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?
今天開始學習程序的靈魂:數據結構與算法。
本文是自己學習極客時間專欄-數據結構與算法之美后的筆記總結。如有侵權請聯系我刪除文章。
我們都知道,數據結構和算法本身解決的是“快”和“省”的問題,即如何讓代碼運行得更快,如何讓代碼更省存儲空間。所以,執行效率是算法一個非常重要的考量指標。那如何來衡量你編寫的算法代碼的執行效率呢?這里就要用到我們今天要講的內容:時間、空間復雜度分析。
復雜度分析是整個算法學習的精髓,只要掌握了它,數據結構和算法的內容基本上就掌握了一半。
1、大O復雜度表示法
算法的執行效率,粗略的說,就是代碼的執行時間。但是實際上代碼在被CPU執行的時候,是相當快的,這個時間我們也無法計算。所以就抽象出了一個用肉眼能夠看到的時間。以例子來分析,看如下一個求和的代碼:
int cal(int n) {int sum = 0;int i = 1;for (; i <= n; ++i) {sum = sum + i;}return sum;}對于CPU來說,它只知道從內存中取指令與執行指令。所以上述代碼,CPU就是一條一條的取指令與執行該指令。現在假設CPU執行每一條的指令的時間都是一樣的為:p_time。那么上述代碼第二三行執行時間2*p_time,四五六行是一個循環。所以第四五行的執行時間是2n*p_time。所以總的執行時間是: (2n+2)*p_time
可以看到:所有代碼的執行時間 T(n) 與每行代碼的執行次數成正比。
按照上述思路,我們再來分析以下代碼:
int cal(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) = (2n2+2n+3)*p_time。
這里我們雖然不知道p_time的具體值,但是很明顯,代碼的總的執行時間T(n)是與n成正比(這里的正比,不是數學的正比,是隨著n的增大,T(n)越來越大)。
我們可以把這種規律,總結成一個規律。此時大O就出現了。
T(n)=O(f(n))T(n) 表示代碼執行的時間;n 表示數據規模的大小;f(n) 表示每行代碼執行的次數總和。因為這是一個公式,所以用 f(n) 來表示。公式中的 O,表示代碼的執行時間 T(n) 與 f(n) 表達式成正比。
所以,第一個例子中的 T(n) = O(2n+2),第二個例子中的 T(n) = O(2n2+2n+3)。這就是大 O 時間復雜度表示法。
注意:大O時間復雜度表示法,并不代表代碼的真正執行時間,而是代表執行的時間隨數據規模增長的一種變化趨勢。 簡稱時間復雜度。
為了簡化時間復雜度的表示,以及由于一些常數,系數以及量級比較小的項對整體的變化影響不大,所以一般將他們去掉。那么以上兩種例子的時間復雜度在簡化以后就是:T(n)=O(n)和T(n)=O(n2)。
2、時間復雜度分析
遇到一段代碼,如何分析它的時間復雜度。一般有三種方法
2.1 只關注循環次數最多的一段代碼
例如如下代碼:
int cal(int n) {int sum = 0;int i = 1;for (; i <= n; ++i) {sum = sum + i;}return sum;}它的時間復雜度就位:T(n)=O(n)
2.2、加法法則
總的時間復雜度等于量級最大的那段代碼的時間復雜度。
看如下代碼:
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;}上述代碼中,第一段代碼的循環為100次,第二段代碼的循環為n次,第三段代碼的時間復雜度為n2次。此時,要注意一點,任何常數次循環,都是O(1時間復雜度),不管是100次,10000次,10000000次,只要能看出是一個具體的數字,它都是O(1)時間復雜度。
由總的時間復雜度等于量級最大的那段代碼的時間復雜度。所以上述代碼最終時間復雜度為O(n2)
結論:
如果 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))).
2.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)).
也就是說,假設 T1(n) = O(n),T2(n) = O(n2),則 T1(n) * T2(n) = O(n3)。落實到具體的代碼上,我們可以把乘法法則看成是嵌套循環,舉個例子給你解釋一下。
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(n2)。
3、幾種常見復雜度分析
3.1、O(1)時間復雜度
如果代碼中沒有循環或者循環的次數是可以確定的常數,那么就是O(1)復雜度
3.2、O(logn) O(nlogn)
看下面的代碼:
i=1;while (i <= n) {i = i * 2;}設執行的次數為x,則2x=n。解得x=log2n
再看下面的代碼:
i=1;while (i <= n) {i = i * 3;}設執行的次數為x,3x=n。解得x=log3n
實際上,不管是以 2 為底、以 3 為底,還是以 10 為底,我們可以把所有對數階的時間復雜度都記為 O(logn)。為什么呢?
我們知道,對數之間是可以互相轉換的,log3n 就等于 log32 * log2n,所以 O(log3n) = O(C * log2n),其中 C=log32 是一個常量。基于我們前面的一個理論:在采用大 O 標記復雜度的時候,可以忽略系數,即 O(Cf(n)) = O(f(n))。所以,O(log2n) 就等于 O(log3n)。因此,在對數階時間復雜度的表示方法里,我們忽略對數的“底”,統一表示為 O(logn)。
如果你理解了前面講的 O(logn),那 O(nlogn) 就很容易理解了。還記得剛講的乘法法則嗎?如果一段代碼的時間復雜度是 O(logn),我們循環執行 n 遍,時間復雜度就是 O(nlogn) 了。而且,O(nlogn) 也是一種非常常見的算法時間復雜度。比如,歸并排序、快速排序的時間復雜度都是 O(nlogn)。
3.1、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 是表示兩個數據規模。我們無法事先評估 m 和 n 誰的量級大,所以我們在表示復雜度的時候,就不能簡單地利用加法法則,省略掉其中一個。所以,上面代碼的時間復雜度就是 O(m+n)。
針對這種情況,原來的加法法則就不正確了,我們需要將加法規則改為:T1(m) + T2(n) = O(f(m) + g(n))。但是乘法法則繼續有效:T1(m)*T2(n) = O(f(m) * f(n))。
4、空間復雜度的分析
前面,花了很長時間講大 O 表示法和時間復雜度分析,理解了前面講的內容,空間復雜度分析方法學起來就非常簡單了。
前面我講過,時間復雜度表示算法的執行時間與數據規模之間的增長關系。類比一下,空間復雜度表示算法的存儲空間與數據規模之間的增長關系。
看如下代碼:
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)。
我們常見的空間復雜度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 這樣的對數階復雜度平時都用不到。而且,空間復雜度分析比時間復雜度分析要簡單很多。
5、總結
復雜度也叫漸進復雜度,包括時間復雜度和空間復雜度,用來分析算法執行效率與數據規模之間的增長關系,可以粗略地表示,越高階復雜度的算法,執行效率越低。常見的復雜度并不多,從低階到高階有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。
本文是自己學習極客時間專欄-數據結構與算法之美后的筆記總結。如有侵權請聯系我刪除文章。
學習探討加個人:
qq:1126137994
微信:liu1126137994
總結
以上是生活随笔為你收集整理的【数据结构与算法-java实现】一 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 北京林业大学计算机技术复试,北京林业大学
- 下一篇: android 支付宝 记账本,使用支付