數據結構與算法之美02

2、算法複雜度分析

如何分析、統計算法的執行效率和資源消耗?算法

時間、空間複雜度分析。數據結構

 爲何須要複雜度分析?數據結構和算法

你可能會有些疑惑,我把代碼跑一遍,經過統計、監控,就能獲得算法執行的時間和佔用的內存大 小。爲何還要作時間、空間複雜度分析呢?這種分析方法能比我實實在在跑一遍獲得的數據更準 確嗎? 首先,我能夠確定地說,你這種評估算法執行效率的方法是正確的。不少數據結構和算法書籍還給 這種方法起了一個名字,叫過後統計法。可是,這種統計方法有很是大的侷限性。函數

1.測試結果很是依賴測試環境。測試

2.測試結果受數據規模影響很大spa

3.大O複雜度表示法code

1 int cal(int n) {
2   int sum = 0;
3   int i = 1;
4   for (; i <= n; ++i) {
5     sum = sum + i;
6   }
7   return sum;
8 }

從 CPU 的角度來看,這段代碼的每一行都執行着相似的操做:讀數據 - 運算 - 寫數據。儘管每行代碼 對應的 CPU 執行的個數、執行的時間都不同,可是,咱們這裏只是粗略估計,因此能夠假設每 行代碼執行的時間都同樣,爲 unit_time 。在這個假設的基礎之上,這段代碼的總執行時間是多少 呢? 第 2 、 3 行代碼分別須要 1 個 unit_time 的執行時間,第 4 、 5 行都運行了 n 遍,因此須要 2nunit_time 的執行時間,因此這段代碼總的執行時間就是 (2n+2)*unit_time 。能夠看出來,全部代 碼的執行時間 T(n) 與每行代碼的執行次數成正比。blog

按照這個分析思路,咱們再來看這段代碼。內存

1 int cal(int n) {
2   int sum = 0;
3   int i = 1;
4   int j = 1;
5   for (; i <= n; ++i) {
6     j = 1;
7     for (; j <= n; ++j) {
8       sum = sum +  i * j;
9     }
10   }
11 }

咱們依舊假設每一個語句的執行時間是 unit_time 。那這段代碼的總執行時間 T(n) 是多少呢? 第 2 、 3 、 4 行代碼,每行都須要 1 個 unit_time 的執行時間,第 5 、 6 行代碼循環執行了 n 遍,須要資源

2n * unit_time的執行時間,第7,8行執行了n^2遍,因此須要2n^2*unit_time的執行時間。因此整段代碼執行時間

T(n)=(2n^2+2n+3)*unit_time.

儘管咱們不知道 unit_time 的具體值,可是經過這兩段代碼執行時間的推導過程,咱們能夠獲得一 個很是重要的規律,那就是,全部代碼的執行時間 T(n) 與每行代碼的執行次數 n 成正比。 咱們能夠把這個規律總結成一個公式。

大O記號

其中, T(n)表示代碼執行的時間; n 表示數據規模的大小; f(n) 表示每行代碼執行的次數總和。由於這是一個公式,因此用 f(n) 來表示。公式中的O ,表示代碼的執行時間 T(n) 與 f(n) 表達式成正比。 因此,第一個例子中的 T(n) = O(2n+2) ,第二個例子中的 T(n) = O(2n +2n+3) 。

這就是大 O 時間複雜度表示法。

大 O 時間複雜度實際上並不具體表示代碼真正的執行時間,而是表示代碼執行時間隨數據規模增加的變化趨勢,因此,也叫做漸進時間複雜度( asymptotic time complexity ),簡稱時間複雜度。 當 n 很大時,你能夠把它想象成 10000 、 100000 。而公式中的低階、常量、係數三部分並不左右增 長趨勢,因此均可以忽略。咱們只須要記錄一個最大量級就能夠了,若是用大 O 表示法表示剛講的 那兩段代碼的時間複雜度,就能夠記爲: T(n) = O(n) ; T(n) = O(n ) 。

時間複雜度分析

如何分析一段代碼的時間複雜度?有三個比較實用的方法。

  1. 只關注循環執行次數最多的一段代碼 大 O 這種複雜度表示方法只是表示一種變化趨勢。咱們一般會忽略掉公式中的常量、低階、係數,只須要記錄一個最大階的量級就能夠了。因此,咱們在分析一個算法、一段代碼的時間複雜度的時候,也只關注循環執行次數最多的那一段代碼就能夠了。這段核心代碼執行次數的 n的量級,就是整段要分析代碼的時間複雜度。

    那前面的第一個例子來講,其中第 2 、 3 行代碼都是常量級的執行時間,與 n 的大小無關,因此對於複雜度並無影響。循環執行次數最多的是第 4 、 5 行代碼,因此這塊代碼要重點分析。前面咱們也講過,這兩行代碼被執行了 n 次,因此總的時間複雜度就是 O(n)

  1. 加法法則:總複雜度等於量級最大的那段代碼的複雜度

//前100個數相加
int cal(int n) {
   int sum_1 = 0;
   int p = 1;
   for (; p < 100; ++p) {
     sum_1 = sum_1 + p;
   }
  //前n個數  
   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;
 }

這個代碼分爲三部分,分別是求 sum_1 、 sum_2 、 sum_3 。咱們能夠分別分析每一部分的時間複雜 度,而後把它們放到一起,再取一個量級最大的做爲整段代碼的複雜度。

第一段的時間複雜度是多少呢?這段代碼循環執行了 100 次,因此是一個常量的執行時間,跟 n 的 規模無關。 這裏我要再強調一下,即使這段代碼循環 10000 次、 100000 次,只要是一個已知的數,跟 n 無 關,照樣也是常量級的執行時間。當 n 無限大的時候,就能夠忽略。儘管對代碼的執行時間會有很 大影響,可是回到時間複雜度的概念來講,它表示的是一個算法執行效率與數據規模增加的變化趨 勢,因此無論常量的執行時間多大,咱們均可以忽略掉。由於它自己對增加趨勢並無影響。 那第二段代碼和第三段代碼的時間複雜度是多少呢?答案是 O(n) 和 O(n )。

綜合三段代碼的時間複雜度,咱們取最大的量級,即總的複雜度爲O(n^2)

也就是說:總的時間複雜度就等於量級最大的那段代碼的時間複雜度。那咱們將這個規律抽 象成公式就是: 若是 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))).

3.乘法法則:嵌套代碼的複雜度等於嵌套內外複雜度的乘積。

若是T1(n)=O(f(n)),T2(n)=O(g(n));

那麼T(n)=T1(n)xT2(n)=O(f(n))xO(g(n))=O(f(n)xg(n)). 也就是說,假設T1(n)=O(n),T2(n)=O(n^2),則T1(n)xT2(n)=O(n^3)。

落實到具體的代碼上

1 int cal(int n) {
2   int ret = 0; 
3   int i = 1;
4   for (; i < n; ++i) {
5     ret = ret + f(i);
6   } 
7 } 
8 
9 int f(int n) {
10  int sum = 0;
11  int i = 1;
12  for (; i < n; ++i) {
13    sum = sum + i;
14  } 
15  return sum;
16 }

咱們單獨看cal()函數。假設f()只是一個普通的操做,那第4~6行的時間複雜度就是,T1(n)=O(n)。

但f()函數自己不是一個簡單的操做,它的時間複雜度是T2(n)=O(n),因此,整個cal)函數的時間複雜度就是,T(n)=T1(n)xT2(n)=O(nxn)=O(n2)。

常見的時間複雜度實例分析

分類:

多項式量級:

非多項式量級:O(2n)和O(n!)。

咱們把複雜度爲非多項式量級的算法問題叫作NP(Non-Deterministic Polynomial,非肯定多項式)問題。

當數據規模n愈來愈大時,非多項式量級算法的執行時間會急劇增長,求解問題的執行時間會無限增加。因此,非多項式時間複雜度的算法實際上是很是低效的算法。所以,關於NP時間複雜度問題略。

主要來看幾種常見的多項式時間複雜度。

Next…

相關文章
相關標籤/搜索