大O時間複雜度表示法

數據結構和算法本身解決的是 ‘快’ 和 '省 ’ 的問題,所以執行效率是算法一個重要的考量標準,如何衡量編寫的算法的執行效率,就要用到所謂的大O表示法。

大 O 複雜度表示法

算法的執行效率,粗略地講,就是算法代碼執行的時間。但是,如何在不運行代碼的情況下,用“肉眼”得到一段代碼的執行時間呢?
這裏有段非常簡單的代碼,求 1,2,3…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;
     }
   }
 }

假設每一行代碼的執行時間爲一個時間單位
第 2、3、4 行代碼,每行都需要 1 個 unit_time 的執行時間,第 5、6 行代碼循環執行了 n 遍,需要 2n * unit_time 的執行時間,第 7、8 行代碼循環執行了 n²遍,所以需要 2n² * unit_time 的執行時間。所以,整段代碼總的執行時間 T(n) = (2n²+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²+2n+3)。這就是大 O 時間複雜度表示法。大 O 時間複雜度實際上並不具體表示代碼真正的執行時間,而是表示代碼執行時間隨數據規模增長的變化趨勢,所以,也叫作漸進時間複雜度(asymptotic time complexity),簡稱時間複雜度。

當 n 很大時,你可以把它想象成 10000、100000。而公式中的低階、常量、係數三部分並不左右增長趨勢,所以都可以忽略。我們只需要記錄一個最大量級就可以了,如果用大 O 表示法表示剛講的那兩段代碼的時間複雜度,就可以記爲: T(n) = O(n²)。

時間複雜度分析

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

 int cal(int n) {
   int sum = 0;
   int i = 1;
   for (; i <= n; ++i) {
     sum = sum + i;
   }
   return sum;
 }

如這段代碼的時間複雜度即爲O(n)

  1. 加法法則:總複雜度等於量級最大的那段代碼的複雜度
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;     
 }   
 }

時間複雜度爲O(n²) 取三個中最大的那個 第一段的時間複雜度爲常量時間與n無關

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

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

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;
 }

時間複雜度爲O(n²)

幾種常見時間複雜度實例分析
在這裏插入圖片描述

  1. O(1)
    首先你必須明確一個概念,O(1) 只是常量級時間複雜度的一種表示方法,並不是指只執行了一行代碼。比如這段代碼,即便有 3 行,它的時間複雜度也是 O(1),而不是 O(3)。

 int i = 8;
 int j = 6;
 int sum = i + j;

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

  1. O(logn)、O(nlogn)

 i=1;
 while (i <= n)  {
   i = i * 2;
 }

在對數階時間複雜度的表示方法裏,我們忽略對數的“底”,統一表示爲 O(logn)。

  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)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章