開啟章節選單
計算時間複雜度
極為常見原則
在計算時間複雜度時,我們會遵循以下原則:
- 只考慮最糟的情況
- 最小的上界
- 忽略常數
- 只考慮最高次項
接下來我們會一一介紹這些原則。
最糟的情況
接著請考慮一個問題,給你一個長度為 的序列,接著給你 個查詢,每個查詢求序列的某一個連續區間 的總和。
首先,如果你已經將前面的章節讀熟,那你應該可以寫出這樣的程式碼:
int n; cin >> n; // 讀序列長度 int a[n + 1]; // 開陣列 for(int i = 1; i <= n; i++) { cin >> a[i]; // 讀入序列 } cin >> q; // 有 q 個查詢 for (int i = 1; i <= q; i++) { int l, r, cnt = 0; cin >> l >> r; // 一筆查詢他的左右界 for (int j = l; j <= r; j++) { cnt += a[j]; // 加總,從 l 跑到 r } cout << cnt << endl; }
那這個時間複雜度是多少呢,我們可以先想如果只有一個查詢,那要從 跑到 ,最糟的情況就是 接近 , 接近 ,在這個情況中,要做一次查詢會從一個長度為 的序列從頭跑到尾,也就是 ,雖然你可能認為他不會都跑滿,但根據 big-O 的定義這個演算法就是一個查詢 。
那因為有 個查詢,所以時間複雜度會增加 倍,也就是 。
最小的上界
如下面這份程式碼是計算 個整數的加總,很容易知道 就是這個演算法的時間複雜度,因為他只是對每個數加一次,而 個數就會加 次。
不過這裡要注意的是,這個 是一個最小的上界,也就是說,這個演算法的時間複雜度不會超過 ,但也有可能是 之類的,不過我們要找的是最小的上界。
int x = 0, y, n; cin >> n; // 輸入 n for (int i = 1; i <= n; i++) { cin >> y; // 輸入一個數 x += y; // 加上去 } cout << x << endl; // 輸出加總
忽略常數
考慮以下這份程式碼:
for (int i = 1; i <= n; i++) { cin >> a[i]; // 讀入序列 } for (int i = 1; i <= n; i++) { a[i] *= 2; } for (int i = 1; i <= n; i++) { a[i] *= 3; }
思考看看,這個時間複雜度是多少呢?我們可以看到第一個迴圈是 ,第二個迴圈也是 ,第三個迴圈是 ,也許你會認為這個是 ,但因為 Big-O 不考慮常數(那個乘與 3 倍的常數),所以這個時間複雜度實際上是 。
只考慮最高次項
接著考慮以下這份程式碼:
for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { cout << "Hello" << endl; } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { for (int k = 1; k <= n; k++) { cout << "Hello" << endl; } } }
這個時間複雜度是多少呢?我們可以看到第一個迴圈是 ,第二個迴圈是 ,或許你會認為這個是 ,但因為 Big-O 只考慮最高次項,所以這個時間複雜度實際上是 。