01 複雜度分析(上) 時間 空間複雜度講解

2021-10-12 01:41:36 字數 4015 閱讀 6121

我們都知道,資料結構和演算法本身解決的是「快」和「省」的問題,即如何讓**執行得更快,如何讓**更省儲存空間。所以,執行效率是演算法乙個非常重要的考量指標。那如何來衡量你編寫的演算法**的執行效率呢?這裡就要用到時間、空間複雜度分析。

1、什麼是複雜度分析?

複雜度也叫漸進複雜度,包括時間複雜度和空間複雜度,用來分析演算法執行效率與資料規模之間的增長關係,可以粗略地表示,越高階複雜度的演算法,執行效率越低。常見的複雜度並不多,從低階到高階有:o(1)、o(logn)、o(n)、o(nlogn)、o(n2 )。

2、為什麼需要進行複雜度分析?

最主要的原因就是事後統計法有非常大的侷限性,主要表現在

測試環境中硬體的不同會對測試結果有很大的影響。比如,我們拿同樣一段**,分別用 intel core i7 處理器和 intel core i3 處理器來執行,不用說,i7 處理器要比 i3 處理器執行的速度快很多。還有,比如原本在這台機器上 a **執行的速度比 b **要快,等我們換到另一台機器上時,可能會有截然相反的結果。

對同乙個排序演算法,待排序資料的有序度不一樣,排序的執行時間就會有很大的差別。極端情況下,如果資料已經是有序的,那排序演算法不需要做任何操作,執行時間就會非常短。除此之外,如果測試資料規模太小,測試結果可能無法真實地反映演算法的效能。比如,對於小規模的資料排序,插入排序可能反倒會比快速排序要快!

ps:我們把**跑一遍,通過統計、監控,就能得到演算法執行的時間和占用的記憶體大小這種方式稱為事後統計法。

3、時間複雜度詳解

時間複雜度又叫做漸進式時間複雜度。我們通常通過大 o 複雜度表示法來表示時間複雜度。在使用大o表示法時,我們可以忽略其低階、常量、係數,則只需要關注迴圈執行次數最多的一段**

時間複雜度可以分為7種型別,分別是:

其中o(1)、o(log n)、o(n)、o(n^2)、o(n log n)、o(n^2)為多項式階級;o(2^n)、o(n!)為非多項式階級。當資料規模 n 越來越大時,非多項式量級演算法的執行時間會急劇增加,求解問題的執行時間會無限增長。所以,非多項式時間複雜度的演算法其實是非常低效的演算法。因此,我們主要來看幾種常見的多項式時間複雜度。

3.1、o(1) 常數階

只要**的執行時間不隨 n 的增大而增長,這樣**的時間複雜度我們都記作 o(1)。或者說,一般情況下,只要演算法中不存在迴圈語句、遞迴語句,即使有成千上萬行的**,其時間複雜度也是ο(1)。

int i = 8;

int j = 6;

int sum = i + j;

3.2、o(log n) 對數階

指的是**的執行次數成對數方式遞增,如下**

i=1;

while (i <= n)

從**中可以看出,變數 i 的值從 1 開始取,每迴圈一次就乘以 2。當大於 n 時,迴圈結束。實際上,變數 i 的取值就是乙個等比數列。所以,這段**的時間複雜度就是 o(log2n),即為o(log n)。

3.3、o(n) 線性階

指的是**的執行次數成線性遞增,如下**

int cal(int n) 

return sum;

}

由**我們可以看出在**中2、3行只會執行一次,則他們的時間複雜度就是o(1),第4、5行執行n次,即時間複雜度為o(2n),總的時間複雜度為o(1+2n),由於在計算時間複雜度時只關注迴圈次數最多的**,並使用大o表示法時可以忽略其低階、常量、係數,則最終的時間複雜度為o(n)。

3.4、o(n log n) 線性對數階

如果一段**的時間複雜度是 o(logn),我們迴圈執行 n 遍,時間複雜度就是 o(nlogn) 了。而且,o(nlogn) 也是一種非常常見的演算法時間複雜度。比如,歸併排序、快速排序的時間複雜度都是 o(nlogn)

for (int i = 0; i < n; i++) 

}

3.5 o(n^k) 次方階 如n^2 平方、n^3 立方

void cal(int n) 

}}

3.6、分析一段**時間複雜度的三種方法

3.6.1 只關注迴圈執行次數最多的一段**

大 o 這種複雜度表示方法只是表示一種變化趨勢。我們通常會忽略掉公式中的常量、低階、係數,只需要記錄乙個最大階的量級就可以了。所以,我們在分析乙個演算法、一段**的時間複雜度的時候,也只關注迴圈執行次數最多的那一段**就可以了。這段核心**執行次數的 n 的量級,就是整段要分析**的時間複雜度。

3.6.2 加法法則:總複雜度等於量級最大的那段**的複雜度

int cal(int n) 

int sum_2 = 0;

int q = 1;

for (; q < n; ++q)

int sum_3 = 0;

int i = 1;

int j = 1;

for (; i <= n; ++i)

}return sum_1 + sum_2 + sum_3;

}

這個**分為三部分,分別是求 sum_1、sum_2、sum_3。我們可以分別分析每一部分的時間複雜度,然後把它們放到一塊兒,再取乙個量級最大的作為整段**的複雜度。

第一段的時間複雜度是多少呢?這段**迴圈執行了 100 次,所以是乙個常量的執行時間,跟 n 的規模無關。

這裡我要再強調一下,即便這段**迴圈 10000 次、100000 次,只要是乙個已知的數,跟 n 無關,照樣也是常量級的執行時間。當 n 無限大的時候,就可以忽略。儘管對**的執行時間會有很大影響,但是回到時間複雜度的概念來說,它表示的是乙個演算法執行效率與資料規模增長的變化趨勢,所以不管常量的執行時間多大,我們都可以忽略掉。因為它本身對增長趨勢並沒有影響。

那第二段**和第三段**的時間複雜度是多少呢?答案是 o(n) 和 o(n2)。

綜合這三段**的時間複雜度,我們取其中最大的量級。所以,整段**的時間複雜度就為 o(n2)。也就是說:總的時間複雜度就等於量級最大的那段**的時間複雜度

4、空間複雜度

空間複雜度全稱就是漸進空間複雜度(asymptotic space complexity),表示演算法的儲存空間與資料規模之間的增長關係。我們常見的空間複雜度就是 o(1)、o(n)、o(n^2),像 o(logn)、o(nlogn) 這樣的對數階複雜度平時都用不到。而且,空間複雜度分析比時間複雜度分析要簡單很多。

具體的例子如下

void print(int n) 

}

跟時間複雜度分析一樣,我們可以看到,第 2 行**中,我們申請了乙個空間儲存變數 i,但是它是常量階的,跟資料規模 n 沒有關係,所以我們可以忽略。第 3 行申請了乙個大小為 n 的 int 型別陣列,除此之外,剩下的**都沒有占用更多的空間,所以整段**的空間複雜度就是 o(n)。

5、為什麼說效能測試和時間、空間複雜度不衝突?

由於做乙個效能測試測試結果會受到資料規模測試機器等外界因素的影響,所以效能測試不是很具有代表性。而時間複雜度和空間複雜度分析是比較客觀的,不會受到外界因素的影響,只有先做了複雜度分析我們才能確實最優方案,最後得到最優解才開始做效能測試。漸進式時間、空間複雜度分析與效能基準測試並不衝突,而是相輔相成的,但是乙個低階的時間複雜度程式有極大的可能性會優於乙個高階的時間複雜度程式,所以在實際程式設計中,時刻關心理論時間、空間度模型是有助於產出效率高的程式的,同時,因為漸進式時間、空間複雜度分析只是提供乙個粗略的分析模型,因此也不會浪費太多時間,重點在於在程式設計時,要具有這種複雜度分析的思維。

本專欄所有的文章已收錄進github中。

複雜度分析(上)時間複雜度 空間複雜度

為了肉眼 實時 快速地來分析出 的複雜度,我們需要乙個不用具體的測試資料來測試,就可以粗略地估計演算法的執行效率的方法。時間複雜度 空間複雜度 表示演算法的執行時間與資料規模之間的增長關係。每行 對應的 cpu 執行的個數 執行的時間都不一樣,但是,我們這裡只是粗略估計,所以可以假設每行 執行的時間...

複雜度分析 時間複雜度 空間複雜度

執行效率是演算法的乙個重要的考量指標,演算法的執行效率用時間 空間複雜度來衡量。今天我們來學習一下複雜度的分析。通常我們可以通過執行程式來獲得演算法的真正的執行時間,這種方法我們可以稱為事後統計法,但這種方法得到的是具體的資料,測試結果很依賴測試環境,而且受資料規模影像最大。因此,我們需要乙個不需要...

複雜度分析 時間複雜度分析和空間複雜度分析

其實,只要講到資料結構與演算法,就一定離不開時間 空間複雜度分析。而且我個人認為,複雜度分析是整個演算法學習的精髓,只要掌握了它,資料結構和演算法的內容基本上就掌握了一半。1.時間複雜度分析 對於剛才羅列的複雜度量級,我們可以粗略地分為兩類,多項式量級和非多項式量級。其中,非多項式量級只有兩個 o ...