C++[2]
目錄
目錄
複雜度
複雜
複雜度的用處
衡量一個程式的效率
有分時間效率與空間效率
怎麼衡量?
\(O()\)漸進符號
高一數學,方程式的廣域特徵
當我做一個三次方程式
把他拉遠


\(y = x^3\)
\(y = x^3+2x^2+3x+1\)
即使方程式不同,圖形大概的趨勢仍然是三次方程式
當 \(x\),也就是輸入的的資料量極大時
則整體趨勢幾乎只受到最大冪次 \(x^3\) 的影響
回到漸進符號
我們把剛剛方程式裡的 \(y\) 想成電腦的運算次數
\(x\) 想成輸入的資料量
假設我現在的程式,他的運算次數 \(y=x^3+20x^2+17x+101\)
當輸入的資料量極大時,運算次數增加的趨勢幾乎只受到\(x^3\)的影響
所以我們稱此程式的複雜度為 \(O(N^3)\)
漸進符號用的資料量通常以 \(N\) 表示
空間複雜度同理
如果運算次數與資料量無關呢?
則複雜度為 \(O(1)\),也就是常數複雜度
衡量完之後要幹嘛
當然就是要判斷你的程式會不會超時了
電腦的計算速度大約為每秒 \(10^9\) 次
拿出剛才的複雜度 \(O(N^3)\)
這時候當 \(N\) 大於 \(\sqrt[3]{10^9}=10^3\)時,這個程式通常無法在 1 秒內跑完
當然還要考慮一下常數的影響
所以這個 \(N\) 的上限一定會再低一點
衡量完之後要幹嘛
對於一個競賽題目中,輸入資料量的上限,至少需要多低複雜度的程式才能在 1~3 秒內跑完

補充:O、Θ、Ω
O(漸進上界),準確來說,算出來的複雜度是指此程式的最糟情況,如果一個程式對於不同的測資會有複雜度上的變動,這時以漸進上界表示出的複雜度會是最糟的那一個(複雜度上界)
如果需要給定一個較為精準的複雜度,而不是保守估計的上界時,就要使用 Θ(漸進精確界)
除了漸進上界外,當然還有漸進下界,也就是 Ω。
此符號表示出的複雜度是指此程式的最佳情況
舉個例子,線性搜尋的複雜度,以三種符號表示為:
\(\Omega(1)\), \(\Theta(n)\), \(O(n)\)
(\(\Theta(n/2) = \Theta(n)\))
上面三個複雜度都是以漸進法計算的
加油
STL
STL
是 C++ 內建的各種資料結構、演算法、迭代器......
反正很好用,也很常用
Vector
向量?
高級陣列
vector 與傳統陣列最大的不同,就是他可以隨時改變大小,有些題目用 vector 會好寫很多
宣告
vector<int>a(n);宣告一個名稱為 a,大小為 n,值的資料型態為 int 的 vector
vector<int>a;也可以不給大小
vector<int>a = {0, 1, 2, 3, 4};
vector<int>b(n, 0);也可以先給定值
也可以初始化(歸零)
宣告
vector<vector<int>>a(n, vector<int(m, 0));二維陣列,n*m 的大小
並歸零
常用語法
vector<int>a;
a.push_back(1);
a.push_back(3);
a.push_back(2);
//a = {1, 3, 2}
a.pop_back();
//a = {1, 3}push_back:從陣列的尾端推一個元素進來
pop_back:把尾端的元素推出去
常用語法
vector<int>a;
for(int i = 0 ; i < 19 ; i++) a.push_back(1);
cout<<a.size() << ' ' << a.capacity(); //19 32
return 0;size:抓目前有使用到的空間大小
capacity:抓目前整個陣列(包含已擴充但未使用部分)
甚麼意思?
常用語法
vector<int>a;
a.push_back(1);
//size=1 capacity=1
a.push_back(1);
//size=2 capacity=2
a.push_back(1);
//size=3 capacity=4
a.push_back(1);
//size=4 capacity=4
a.push_back(1);
//size=5 capacity=8每次push_back時,如果空間不夠,就會先把實際大小擴充成原本的兩倍
常用語法
vector<int>a;
auto bit = a.begin();
auto eit = a.end();a.begin():陣列中第一個值的 iterator
a.end():陣列中最後一個值的 iterator
sort(a.begin(), a.end());
reverse(a.begin(), a.end());可用於 sort, reverse, upper/lower bound 等地方
不好用的語法
vector<int>a(2, 3);
a.insert(1, 4);
//a = {3, 4, 3}a.insert(位置, 數值);
在陣列中的一個位置插入一個數值
\(O(N)\),爛
vector<int>a(15, 3);
a.erase(a.begin() + 3, a.end());
//a = {3, 3, 3}
a.erase(a.begin());
a = {3, 3}a.erase(iterator, iterator);
把陣列中的一段數值刪除
即使只有刪除一個數值,複雜度還是\(O(N)\),還是爛
題目
pair
一對
一次存兩個變數
一個 pair 有兩個變數,而且資料型態可以不同
像是我可以把一個人名配對一個分數,再對他做排序等動作,兩個被包在一起的元素就會一起移動
宣告
pair<string, int>a;宣告一個包著 string 與 int 的 pair
pair<string, int>a("wut", 17);可以初始化
vector<pair<int, int>>a;還能包進其他 STL 裡
語法
pair<string, int>a("wut", 17);
a.first = "eee";
cout << a.first; //eee
cout << a.second; //17pair<string, int>a;
a = make_pair("idk", 123);make_pair():生成一個 pair
遍歷
便利
python for
a = [1, 2, 4, 3, 5]
for i in a:
print(i, end = ' ')
#1 2 4 3 5如果你想要把整個陣列的數字一個一個輸出,在 python 中有這種寫法
注意這時候的 i ,他不像一般的 for 迴圈裡的 i,是一個 int而是跑到陣列裡面變成陣列裡的數值
稱為迭代器
要怎麼用 C++ 寫?
C++ for
vector<int> a = {1, 2, 3, 4, 5};
for (int i = 0 ; i < a.size() ; i++){
cout << v[i] << ' ';
}
//1 2 3 4 5vector<int> a = {1, 2, 3, 4, 5};
for(vector<int>::iterator it = a.begin() ; it != a.end() ; it++){
cout << *it << " ";
}
//1 2 3 4 5欸不是這太噁心了吧
還記得 auto 嗎
vector 有自己迭代器的資料型態
C++ for
vector<int> a = {1, 2, 3, 4, 5};
for(auto it = a.begin() ; it != a.end() ; it++){
cout << *it << " ";
}
//1 2 3 4 5還是沒有比原本的好寫啊?
vector<int> a = {1, 2, 3, 4, 5};
for(auto i : a){
cout << i << " ";
}
//1 2 3 4 5這很簡短了
基本上這跟 python 的寫法是很像的
a = [1, 2, 4, 3, 5]
for i in a:
print(i, end = ' ')
#1 2 4 3 5stack
疊
一疊椅子

你們應該知道這種椅子
當你要拿走椅子的時候,都會是從最上面拿
當你要放椅子的時候,也是從最上面放
想一下現在有一張椅子,他是第一個放進去的
當你要拿椅子的時候,他是不是在最下面,最後一個才能拿到
這種先進後出(LIFO)的資料結構即為 stack
宣告
stack<int>st;stack 無法初始化
常用語法
stack<int>st;
st.push(1);
st.push(3);
cout << st.top(); //3st.push():像疊椅子那樣,把變數疊起來
st.top():獲取 stack 中最上面的那個元素
常用語法
stack<int>st;
cout << st.empty(); //true
cout << st.size(); //0
st.push(1);
st.pop();
cout << st.top(); //segment faultst.empty():回傳一個布林值代表 stack 是否為空
st.size():回傳 stack 目前的大小
st.pop():把最上面的椅子(元素)抽出來丟掉
注意,當 stack 為空時,st.top() 會爆炸
注意事項
stack<int>st;
st.push(1);
cout << st[0]; //報錯
stack 無法用中括號索引取值
題目
queue
排隊
概念
想像一下你在排隊
越早到的人排得越前面
排得越前面的人能先拿到東西,然後離開隊伍
這種先進先出(FIFO)的資料結構即為 queue
宣告
就這樣
一樣無法初始化
queue<int>q;常用語法
queue<int>q;
q.push(1);
q.push(2);
cout << q.front(); //1
q.pop();
cout << q.front(); //2
cout << q.size(); //1
cout << q.empty(); //falseq.push():丟一個元素進去排隊
q.front():回傳排隊排得最前面的元素
q.pop():把最前面的元素踢掉
q.size():回傳 queue 目前的大小
q.empty():回傳 queue 是否為空
注意事項
queue<int>q;
q.push(1);
cout << q[0]; //報錯
queue 也無法用中括號索引取值
題目
priority_queue
概念
還是想像你在排隊
但是年紀越大的排越前面
當有一個人排進來的時候,會依據自己的年齡插隊到正確的位置
宣告
priority_queue<int>pq;字有點多,可能要記一下
常用語法
priority_queue<int>pq;
pq.push(1);
pq.push(2);
cout << pq.top(); //2
pq.pop();
cout << pq.top(); //1
pq.push(0);
cout << pq.top(); //1
cout << pq.empty(); //false
cout << pq.size(); //2pq_push():把元素丟進隊伍裡
pq.top():獲取排得最前的值(就是最大值)
pq.pop():把最前面的值踢出去
pq.size():獲取當前 priority_queue 的大小
pq_empty():回傳當前貯列是否為空
注意事項
priority_queue<int>pq;
pq.push(1);
cout << pq[0]; //報錯
priority_queue 也無法用中括號索引取值
題目
list
還記得 insert 跟 erase 嗎
概念
在 Vector 中,一個元素裡存放一個值
而在 List 中,一個元素裡存放一個值、以及左右一格元素的指標
與 vector 相比,他不能直接使用索引值抓取元素
但是像 insert, erase 這種操作,它們單次操作的複雜度僅有\(O(1)\)
常見語法
l.push_front():從前面推入一個元素
l.pop_front():把最前面的元素拿出來丟掉
l.front():獲取第一個元素的值
l.back():獲取最後一個元素的值
其他的語法跟 vector 都是一樣的
list<int>l;
l.push_back(1);
l.push_back(3);
l.insert(1, 2);
cout << l.front(); //1
cout << l.back(); //3
l.push_front(0);
cout << l.front(); //0
l.pop_back();
l.pop_front();
//l = {1, 2}
cout << l.size() //2
cout << l.empty() //false
l.insert(1, 2);
l.erase(l.begin() + 1, l.end());
//l = {1}迭代器
list<int>l(5);
for (auto &i : l) {
cin >> i;
}
for (auto i : l) {
cout << i << " ";
}取值
list<int> l = {1, 2, 3, 4, 6};
auto it = l.begin();
advance(it, 4);
cout << *it;//6list<int> l = {1, 2, 3, 4, 6};
cout << l[3];剛剛說了不行
這個才行
但是advance() 是 O(n)
MAP
地圖
與 array 的不同之處
array 的 index 是固定的一串遞增的數字
而 map 可以指定一個 index,與其對應的值
index 也可以不是數字
map 語法
map<int, int>m;宣告
第一個 int 為 index 的資料型態
第二個 int 為值的資料型態
map 語法
map<string, int>m;
m["ayo"] = 27;
cout << m["ayo"]; //27index 資料型態為 string 的範例
要新增 index 的話不用額外操作
直接向第二行那樣就好
例題
喔對了這是這學期資訊能力競賽初賽的第一題
報寒訓!!
C++[2]
By 硼/Boron
C++[2]
- 119