時間複雜度
yennnn
如何衡量一支程式碼的效能?
如何衡量一支程式碼的效能?
- 執行所需時間
- 記憶體空間
- 編程難易度
- ......
如何衡量一支程式碼的效能?
- 執行所需時間
- 記憶體空間
- 編程難易度
- ......
編程時間
占用的記憶體空間
實際執行一次花了多久
如果我們以實際執行時間來描述程式的效能......
- 每次跑會一樣嗎?
- 在不同的電腦上會一樣嗎?
- 輸入不同的時候會一樣嗎?
我們需要一個更一般化的理論來分析
如果我們以程式的步驟數來描述程式的效能......
- 不受電腦硬體的影響
- 可能受輸入規模影響
- 可以把步驟數想成一個函數\(T(n),n是輸入規模\)
怎麼算\(T(n)\)呢?
計算\(T(n)\)
- 我們把變數的宣告、運算、比較等等各種操作都均等的視為1個步驟
- \(T(n) = 程式從開始執行到結束總共經過幾個步驟\)
- 注意迴圈、遞迴等等
計算\(T(n)\)
Ex1:n個整數的總和
int sum(int list[], int n){
int i;
int result = 0;
for(int i = 0; i < n; i++){
result = result + list[i];
}
return result;
}
Ex1:n個整數的總和
int sum(int list[], int n){
int i;
int result = 0;
for(int i = 0; i < n; i++){
result = result + list[i];
}
return result;
}
\(1\)
\(1\)
\(2(n + 1) + 1\)
\(n\)
\(1\)
每行計算步驟數
\(3n + 6\)
\(T(n) = 3n + 6\)
Ex2:兩個二維陣列相加
array_add(int m, int n, int A[m][n], int B[m][n], int C[m][n]){
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
C[i][j] = A[i][j] + B[i][j];
}
}
}
Ex2:兩個二維陣列相加
array_add(int m, int n, int A[m][n], int B[m][n], int C[m][n]){
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
C[i][j] = A[i][j] + B[i][j];
}
}
}
每行執行步驟
\(2(m + 1) + 1\)
\(2(m + 1) + 1\)
\(2m(n + 1) + 1\)
\(mn\)
\(T(n) = 2mn + 4m + 4\)
#include <iostream>
#define MAXN 200100
#define INF 2147483647
using namespace std;
int arr[MAXN];
class segment_tree {
public:
void init(int n) { fill(seg_arr, seg_arr + 4 * n, INF); }
void build(int l, int r, int cur) {
if (l == r) {
seg_arr[cur] = arr[l];
return;
}
int mid = (l + r) / 2;
build(l, mid, cur * 2);
build(mid + 1, r, cur * 2 + 1);
seg_arr[cur] = min(seg_arr[cur * 2], seg_arr[cur * 2 + 1]);
return;
}
void modify(int l, int r, int ind, int val, int cur) {
if (l == r && l == ind) {
arr[ind] = val;
seg_arr[cur] = val;
return;
}
int mid = (l + r) / 2;
if (ind <= mid) {
modify(l, mid, ind, val, cur * 2);
} else {
modify(mid + 1, r, ind, val, cur * 2 + 1);
}
seg_arr[cur] = min(seg_arr[cur * 2], seg_arr[cur * 2 + 1]);
return;
}
int query(int l, int r, int ql, int qr, int cur) {
if (l > r || ql > r || qr < l) return INF;
if (l >= ql && r <= qr) return seg_arr[cur];
int mid = (l + r) / 2;
// cout << l << ' ' << r << '\n';
return min(query(l, mid, ql, qr, cur * 2),
query(mid + 1, r, ql, qr, cur * 2 + 1));
}
void print(int n) {
for (int i = 0; i <= 4 * n; i++) {
cout << "seg_arr[" << i << "] = " << seg_arr[i] << '\n';
}
}
protected:
int seg_arr[MAXN * 4];
};
segment_tree seg;
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n, q;
cin >> n >> q;
fill(arr, arr + n + 10, INF);
seg.init(n);
for (int i = 1; i <= n; i++) {
cin >> arr[i];
}
seg.build(1, n, 1);
// seg.print(n);
while (q--) {
int m, a, b;
cin >> m >> a >> b;
if (m == 1) {
seg.modify(1, n, a, b, 1);
} else {
cout << seg.query(1, n, a, b, 1) << '\n';
}
}
return 0;
}
Ex3: 線段樹
這樣要怎麼分析?==
\(Big \ \mathcal{O} \ notation\)
- 精確算出\(T(n)\)實在太麻煩了
- 我們也不需要那麼精確的得知\(T(n)\)即可估算時間複雜度
- 找到一個簡單的函數\(f(n)\)來代表所有\(T(n)和f(n)\) 差不多 的程式
- 我們會用\(Big \ \mathcal{O} \ notation\) 這樣的表達方式來表達這樣的近似關係
- Eg : \( \mathcal{O} (n) \)
時間複雜度
有時候我們在估計程式的時間複雜度\(T(n)\)時
其實也沒有那麼在意程式在每個\(n\)的精確表現
我們更在意的是\(T(n)\)隨\(n\)的成長趨勢
- 表達一個函數的 漸近上界
- 以\(\mathcal{O} (f(n)) \)表示 \(f(n)是T(n)的漸近上界\)
- 隨著\(n\)的成長,\(T(n)\)也會隨之成長,但\(T(n)\)的成長不會超過\(f(n)\)
一些常見的複雜度比較
複雜度的成長趨勢
一些常見的複雜度比較
成長越快
階數越高
\(Big \ \mathcal{O} \ notation\)估算法則
-
加法定則
-
數個函數相加,取階數最高者代表複雜度
-
-
乘法定則
-
函數乘以常數可省略
-
\(Big \ \mathcal{O} \ notation\)估算法則
-
加法定則
-
\(n\)
-
\(n^2 + n\)
-
\(n + \log n\)
-
\(n + n \log n\)
-
\(2^n + n^2\)
-
\(\mathcal{O}(n)\)
\(\mathcal{O}(n^2)\)
\(\mathcal{O}(n)\)
\(\mathcal{O}(n \log n)\)
\(\mathcal{O}(2^n)\)
\(Big \ \mathcal{O} \ notation\)估算法則
-
乘法定則
-
\(n\)
-
\(2n\)
-
\((\log 2) + n\)
-
\((\log 2)n\)
-
\(\log (2n)\)
-
\(\log_{7122} n\)
-
\(\mathcal{O}(n)\)
\(\mathcal{O}(n)\)
\(\mathcal{O}(n)\)
\(\mathcal{O}(\log n)\)
\(\mathcal{O}(n)\)
\(\mathcal{O}(\log n)\)
\(Big \ \mathcal{O} \ notation\)估算實戰
-
快速估算仰賴對基本演算法與資料結構的複雜度認識
- Ex :
- sort : \( \mathcal{O} (n \log n)\)
- 二分搜 : \( \mathcal{O} ( \log n)\)
- set插入 : \( \mathcal{O} ( \log n)\)
- dijkstra : \( \mathcal{O} ( (E + V) \log V)\)
- kruskal : \( \mathcal{O} (E \log V)\)
- ......
vector<int> a;
sort(a.begin(), a.end());
\( \mathcal{O} (n \log n)\)
vector<int> a[n];
for(int i = 0; i < n; i++)
sort(a[i].begin(), a[i].end());
\( \mathcal{O} (n^2 \log n)\)
vector<int> a[n];
for(int i = 0; i < n; i++)
a[i].push_back(1);
sort(a[i].begin(), a[i].end());
\( \mathcal{O} (n + n \log n) = \mathcal{O} (n \log n)\)
時間複雜度
By yennnn
時間複雜度
0325資訊社 社課講義--時間複雜度 Time Complexity Designed By Yennnn
- 311