時間與空間複雜度分析

Silence-jk發表於2018-12-09

空間複雜度

概念

表示演算法的儲存空間與資料規模間的增長關係

分析方法

只關注這段程式碼中申請的最大空間

void print(int n) {
  int i = 0;
  int[] a = new int[n];
  for (i; i < n; ++i) {
    a[i] = i * i
  } 
}
複製程式碼

上面程式碼第二行申請了一個空間儲存 i,它是常量階的,跟 n 無關,可以忽略。 第三行申請了一個大小為 n 的 int 陣列,除此之外,沒有申請其他空間,所以這段程式碼的空間複雜度為 O(n)。

時間複雜度

概念

表示演算法的執行時間與資料規模之間的增長關係

分析方法

  • 只關注迴圈次數最多的一段程式碼
/**
 * 函式中第一、二行程式碼執行 1 次
 * 第三、四行程式碼執行了 n 次,所以總複雜度為 O(n)
 */
function cal(n) {
  let sum = 0
  let i = 1
  for (; i <= n; i++) {
    sum += i
  }
  return sum
}

複製程式碼
  • 加法法則:總複雜度等於量級最大的那段程式碼的複雜度
/**
 * 第一個 for 迴圈複雜度為常量級 O(1),第二個是 n,複雜度為 O(n)
 * 所以總複雜度為 O(n)
 * 注:只要是一個已知數(如:100,10000,1000000)都是常量級,複雜度為O(1) 
 */
function cal(n) {
  let i = 1
  let sum_1 = 0
  for (; i <= 100; i++) {
    sum_1 += i
  }

  let sum_2 = 0
  for (; i <= n; i++) {
    sum_2 += i
  }
  return sum_1 + sum_2
}
複製程式碼
  • 乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積
/**
 * cal 中巢狀了函式 fn,fn 的總複雜度為 O(n),所以 cal 的總複雜度為 O(n²)
 */
function cal(n) {
  let i = 1
  let sum = 0
  for (; i <= n; i++) {
    sum += fn(i)
  }
}

function fn(n) {
  let sum = 0
  for (; i <= n; i++) {
    sum += i
  }
  return sum
}
複製程式碼

對數階時間複雜度分析

let i = 1
while (i <= n) {
  i = i * 2
}
複製程式碼

根據上面的分析方法,找出執行次數最多的一行,為第 3 行; 可以看出 i 的值為每次乘以 2 的等比數列。當 2 的 k 次方大於 n 時,結束迴圈。

k 就為第 3 行的最大執行次數 2^k = n, k = log 以 2 為底的 n 次方。因為對數之間可以相互轉換,不管以什麼為底,我們都統一記為 O(logn)

最好、最壞情況時間複雜度

/**
 * @param {Array} arr 
 * @param {Number} n 陣列的長度
 * @param {*} x 要查詢的值
 */
function find (arr, n, x) {
  let i = 0
  let pos = -1
  for (i; i < n; i++) {
    if (arr[i] === x) {
      pos = i
      break
    }
  }
  return pos
}
複製程式碼

上面這段程式碼在 arr[0] === x 時,為最好情況,此時時間複雜度為 O(1);
arr[n] === x 時,為最壞情況,此時時間複雜度為 O(n)

平均情況時間複雜度

上節程式碼要查詢 x 在陣列中的位置,有 n+1 種情況,分別是 x 在 0 ~ n-1 的位置中和不在陣列中。

x 在索引為 0 處時,遍歷 1 次,在索引為 1 時遍歷 2 次,總遍歷次數為 1 + 2 + 3 + ... + n + (n+1) = n(n+2)/2。(等差數列求和公式)。

平均每種情況要遍歷的次數為 n(n+2)/2(n+1) ≈ n/2,平均情況的時間複雜度就為 O(n)

參考:資料結構與演算法之美

相關文章