把**跑一遍,通過統計、監控,就能得到演算法執行的時間和占用的記憶體大小。資料結構和演算法書籍管這種方法叫事後統計法。
事後統計法的侷限性:
測試結果非常依賴測試環境;
測試結果受資料規模的影響很大。
1從 cpu 的角度來看,這段**的每一行都執行著類似的操作:讀資料-運算-寫資料。儘管每行**對應的 cpu 執行的個數、執行的時間都不一樣,但是,我們這里只是粗略估計,所以可以假設每行**執行的時間都一樣,為 unit_time。在這個假設的基礎之上,這段**的總執行時間是多少呢?function
cal(n)
7return
sum;
8 }
第 2、3 行**分別需要 1 個unit_time的執行時間,第 4、5 行都運行了 n 遍,所以需要2n*unit_time的執行時間,所以這段**總的執行時間就是(2n+2)*unit_time。可以看出來,所有**的執行時間 t(n) 與每行**的執行次數成正比。
1我們依舊假設每個語句的執行時間是 unit_time。那這段**的總執行時間 t(n) 是多少呢?第 2、3、4 行**,每行都需要 1 個unit_time的執行時間,第 5、6 行**迴圈執行了 n遍,需要2n * unit_time的執行時間,第 7、8 行**迴圈執行了 n2遍,所以需要2n2 *unit_time的執行時間。所以,整段**總的執行時間t(n) = (2n2+2n+3)*unit_time。function
cal(n)10}
11return
sum;
12 }
儘管我們不知道 unit_time 的具體值,但是通過這兩段**執行時間的推導過程,我們可以得到乙個非常重要的規律,那就是,所有**的執行時間 t(n) 與每行**的執行次數 n 成正比,即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 時間複雜度實際上並不具體表示**真正的執行時間,而是表示**執行時間隨資料規模增長的變化趨勢,所以,也叫作漸進時間複雜度(asymptotic time complexity),簡稱時間複雜度。
當 n 很大時,你可以把它想象成 10000、100000。而公式中的低階、常量、係數三部分並不左右增長趨勢,所以都可以忽略。我們只需要記錄乙個最大量級就可以了,如果用大 o 表示法表示剛講的那兩段**的時間複雜度,就可以記為:t(n) = o(n);t(n) = o(n2)。
1. 只關注迴圈執行次數最多的一段**。
在第乙個例子中,中第 2、3 行**都是常量級的執行時間,與 n 的大小無關,所以對於複雜度並沒有影響。迴圈執行次數最多的是第 4、5 行**,所以這塊**要重點分析。前面我們也講過,這兩行**被執行了 n 次,所以總的時間複雜度就是 o(n)。
2. 加法法則:總複雜度等於量級最大的那段**的複雜度。
在第二個例子中,我們取其中最大的量級。所以,整段**的時間複雜度就為o(n2)。也就是說:總的時間複雜度就等於量級最大的那段**的時間複雜度。
3. 乘法法則:巢狀**的複雜度等於巢狀內外**複雜度的乘積。
1在這個例子中,單獨看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)。function
cal(n)
7return
ret8}9
function
f(n)
15return
sum16 }
常見的複雜度量級可以粗略的分為兩類:多項式量級和非多項式量級
多項式量級:
非多項式量級:
o(1) 只是常量級時間複雜度的一種表示方法,並不是指只執行了一行**。比如下面這段**,即便有 3 行,它的時間複雜度也是 o(1),而不是 o(3)。
1 let i=8;只要**的執行時間不隨 n 的增大而增長,這樣**的時間複雜度我們都記作o(1)。或者說,一般情況下,只要演算法中不存在迴圈語句、遞迴語句,即使有成千上萬行的**,其時間複雜度也是ο(1)。2 let j=6;
3 let sum = i+j;
1 let i=0;根據我們前面講的複雜度分析方法,第三行**是迴圈執行次數最多的。所以,我們只要能計算出這行**被執行了多少次,就能知道整段**的時間複雜度。2 while(i<=n)
從**中可以看出,變量 i 的值從 1 開始取,每迴圈一次就乘以 2。當大於 n 時,迴圈結束。還記得我們高中學過的等比數列嗎?實際上,變量 i 的取值就是乙個等比數列。如果我把它乙個乙個列出來,就應該是這個樣子的:
我們只要知道 x 值是多少,就知道這行**執行的次數了。通過 2 =n 求解 x,x= log2n,所以這段**的時間複雜度就是o(log2n)
如果把上面的**稍微修改一下:
1 let i=0;這段**的時間複雜度就是o(log3n)了。2while(i<=n)
實際上,不管是以 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),我們迴圈執行 n 遍,時間複雜度就是 o(nlogn)了。而且,o(nlogn) 也是一種非常常見的演算法時間複雜度。比如,歸併排序、快速排序的時間複雜度都是 o(nlogn)。
1從**中可以看出,m 和 n 是表示兩個資料規模。我們無法事先評估 m 和 n 誰的量級大,所以我們在表示複雜度的時候,就不能簡單地利用加法法則,省略掉其中乙個。所以,上面**的時間複雜度就是 o(m+n)。function
cal(m,n)
78 let sum2 = 0;
9 let j = 1;
10for(;j)
13return sum1 +sum2
14 }
針對這種情況,原來的加法法則就不正確了,我們需要將加法規則改為:t1(m) + t2(n) =o(f(m) + g(n))。但是乘法法則繼續有效:t1(m)*t2(n) = o(f(m) * f(n))。
複雜度分析 時間複雜度分析和空間複雜度分析
其實,只要講到資料結構與演算法,就一定離不開時間 空間複雜度分析。而且我個人認為,複雜度分析是整個演算法學習的精髓,只要掌握了它,資料結構和演算法的內容基本上就掌握了一半。1.時間複雜度分析 對於剛才羅列的複雜度量級,我們可以粗略地分為兩類,多項式量級和非多項式量級。其中,非多項式量級只有兩個 o ...
複雜度分析 時間複雜度 空間複雜度
執行效率是演算法的乙個重要的考量指標,演算法的執行效率用時間 空間複雜度來衡量。今天我們來學習一下複雜度的分析。通常我們可以通過執行程式來獲得演算法的真正的執行時間,這種方法我們可以稱為事後統計法,但這種方法得到的是具體的資料,測試結果很依賴測試環境,而且受資料規模影像最大。因此,我們需要乙個不需要...
複雜度分析(上)時間複雜度 空間複雜度
為了肉眼 實時 快速地來分析出 的複雜度,我們需要乙個不用具體的測試資料來測試,就可以粗略地估計演算法的執行效率的方法。時間複雜度 空間複雜度 表示演算法的執行時間與資料規模之間的增長關係。每行 對應的 cpu 執行的個數 執行的時間都不一樣,但是,我們這裡只是粗略估計,所以可以假設每行 執行的時間...