Commonly Used Function
STL
abs(x) 回傳x的絕對值
sqrt(x) 回傳x的平方根(浮點數)
pow(a,x) 回傳a的x次方浮點數)
ceil(x)/floor(x)/round(x) 回傳x向上取整/向下取整/四捨五入
三角函數 cos(x) sin(x) tan(x)
反三角函數 acos(x) asin(x) atan(x) atan2(y, x)
數值單位全部都是弧度(radians)
角度 = 弧度 / (2π)*360
常用 pi = acos(-1)
#define PI acos(-1)
exp(x) 自然底數 e 的 x 次方
log(x) 以 e 為底的對數
log10(x) 以 10 為底的對數
log2(x) 以 2 為底的對數
要換底請用換底公式
#include<algorithm>
input
3 9 4 1 5
result
1 3 4 5 9
#include<algorithm>//min/max min(a, b) min({a, b, c}) max(a, b) max({a, b, c, d});
#include<algorithm> swap(a, b)
(需 c++14 後才能使用)
#includ<algorithm> //gcd #include<iostream> int main(){ int a, b; cin >> a >> b; cout << __gcd(a, b) << endl; }
a 和 b 的最小公倍數 = a * b / gcd(a, b)
把區間 [l, r) 的值都設成 x,l, r 為指標或者迭代器
#include<iostream>
#include<algorithm>
int main(){
int n = 5;
int arr[8]={0,1,2,3,4,5,6,7};
fill(arr+1, arr+5, 10);
}
arr 的結果為 {0,10,10,10,10,5,6,7}
排序區間 [l, r),l, r 為指標或者迭代器
sort(l, r);
#include<iostream> #include<algorithm> // sort int arr[48763]={4, 8, 7, 6, 3}; int main(){ sort(arr,arr+5); }
結果為 3, 4, 6, 7, 8
如果不想由小排到大,想由大排到小?
寫一個函式,
告訴編譯器你要怎麼排!
傳入兩個參數 lhs, rhs,作為比較對象
lhs(left hand side) 代表較左邊的元素
rhs(right hand side) 代表較右邊的元素
可以想成希望在左邊的元素大於右邊的元素,
#include<iostream> #include<algorithm> // sort bool cmp(int lhs, int rhs){ // compare function return lhs > rhs; } int arr[5] = {3, 2, 7}; int main(){ sort(arr, arr+3, cmp); }
給你 \(n\) 個整數與整數 \(m\),排序順序如下 :
mod m
由小到大mod m
由小到大bool cmp(int lhs, int rhs){ if((lhs % m) != (rhs % m)) return (lhs % m) < (rhs % m); if ((lhs%2) != (rhs%2)) return (lhs%2) > (rhs%2); if(lhs%2) return lhs > rhs; else return lhs < rhs; }
把區間 [l, r) 翻轉,l, r 為指標或迭代器
#include<algorithm> //reverse int arr[5] = {1,2,3,4,5}; reverse(arr, arr+5);
arr 的結果為 {5,4,3,2,1}
next_permutation 產出的下一個排列都會比原本的字典序還大,而 prev_permutation 則相反
如果一開始的排列是字典序最小的,就可以用 next_permutation 產生出全部的排列。
產生區間 [l, r) 的下一個排列,若不存在則回傳 false,反之回傳 true
均攤複雜度 \(Ω(1)\),最差 \(O(n)\)
input
1 3 2
result
2 1 3
#include<algorithm> int arr[4] = {3,2,4,1}; next_permutation(arr, arr+4);
result
3 4 1 2
如果序列一開始為最小字典序,
透過 next_permutation 不斷變成下一大的順序,
可以產生所有排序
int n = 3; int arr[3] = {1, 2, 3}; do{ for(int i = 0; i < n; i++) cout << arr[i] << " \n"[i+1==n]; } while(next_permutation(arr, arr + n));
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include<cctype>
islower(c) 判斷字元 c 是否為小寫字母
isupper(c) 判斷字元 c 是否為大寫字母
isalpha(c) 判斷字元 c 是否為字母
isdigit(c) 判斷字元 c 是否為數字
tolower(c) 回傳字元 c 的小寫字母
toupper(c) 回傳字元 c 的大寫字母
nth_element(l, l+k, r)
將此區間 [l, r) 第 k 小的數放在左邊數來第 k 個位置,
且 [l , k) 皆比 k 小,(k , r) 皆比 k 大,
但這兩個區間內是無序的
int arr[1e5+5]={}; bool cmp(int x,int y){ return x>y; //更改為降序排列 } int main(){ for(int i=1;i<=100;i++) arr[i]=i; nth_element(arr+1,arr+50+1,arr+100+1); }
結果 : 1~49 (無序排列) , 50 , 51~100 (無序排列)
複雜度為平均 \(O(n)\)
可以加入第四個函數傳入自訂比較函數 nth_element(l, l+k, r, cmp)
#include<iostream> #include<algorithm> int n,k,arr[100005]; int main(){ cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i]; nth_element(a+1,a+k,a+n+1,greater<int>()); cout<<a[k-1]; return 0; }
greater<> 為內建的大於比較函式
找到區間 [l, r) 範圍內第一個大於等於 x 的值的位置。
找到區間 [l, r) 範圍內第一個大於 x 的值的位置。
以上兩個函數裡面所尋找的區間必須為已排序的才可以正常使用
如果沒找到則會回傳位置 r (最後一項的下一項)
時間複雜度 \(O(\log n)\)
#include<iostream> #include<algorithm> using namespace std; int a[100005]; int main(){ int n,x; cin>>n; for(int i=0;i<n;++i)cin>>a[i]; sort(a,a+n); cin>>x; cout<<lower_bound(a,a+n,x)-a<<'\n'; cout<<upper_bound(a,a+n,x)-a<<'\n'; //通過找到的位置減去起始位置begin,得到索引值 return 0; }
一樣可以使用自訂比較函式
lower_bound ( first , last , value , cmp)
upper_bound ( first , last , value , cmp)
把區間內連續相同元素刪除到只留一個,
回傳值為沒被刪除的元素的下一項 指標/迭代器
複雜度 \(O(n)\)
input
4 2 1 1 1 5 2 2 4 4
result
4 2 1 5 2 4 x x x x
注意原陣列大小不會改變,相異元素之後的值無法保證為何
unique(l, r) // 將區間 [l, r) 內連續相同元素刪除到只留一個,
#include<iostream> #include<algorithm> int n = 10; int arr[10] = {4, 2, 1, 1, 1, 5, 2, 2, 4, 4}; int main(){ sort(arr, arr+n); int k = unique(arr, arr+n) - arr; cout << k << endl; for(int i = 0; i < k; i++) cout << arr[i] << " \n"[i+1==k]; }
result
6
4 2 1 5 2 4
常用於離散化,把一段的區間 [l, r) 的資料只留下相異的元素各一個
作法
結果為原始陣列所有相異元素並且已排序
#include<iostream> #include<algorithm> int n = 10; int arr[10] = {4, 2, 1, 1, 1, 5, 2, 2, 4, 4}; int main(){ sort(arr, arr+n); int k = unique(arr, arr+n) - arr; cout << k << endl; for(int i = 0; i < k; i++) cout << arr[i] << " \n"; }
result
4
1 2 4 5
分別在 <utility> 和 <tuple> 中
偷懶用資料結構,分別是可以用來宣告裝 2 個東西/多個東西的變數
常用來儲存二維平面上的點 (x, y) 之類
pair<int,int>
pair<double,int>
pair<int,int> a a.first (可以取到第一個值) a.second (可以取到第二個值)
對 pair 的元素 sort,會先比較 first 大小,相同再比較 second 的大小
#include<iostream>
#include<utility>
using namespace std;
pair<int, int> point[10];
int main(){
for(int i = 0; i < 5; i++) cin >> point[i].first >> point[i].second;
sort(point, point+5);
int a, b;
point[0] = make_pair(a, b);
}
tuple<int, int, int>
tuple<double, int, long double, ...(還可以塞很多東西)>
tuple<int, int, int> a get<0>(a) (可以取到第一個值) get<2>(a) (可以取到第三個值)
make_tuple 可以把元素合併成 tuple
#include<iostream>
#include<tuple>
using namespace std;
tuple<string, int, int, double> item[10];
int main(){
string s; int a, b; double d;
for(int i = 0; i < 5; i++){
cin >> s >> a >> b >> d;
item[i] = make_tuple(s, a, b, d);
}
sort(item, item+5);
tie(s, a, b, d) = item[0];
cout << s << " " << b << " " << d << " " << get<0>(item[1]) << endl;
}
迭代器是用來遍歷容器的東西,由於有些容器不像陣列能用下標 (index) 遍歷,迭代器便能用來遍歷這類容器。
c++ 的迭代器皆被實作的與指標相似,用遞增運算子 (++it / it++) 來移動到下一項,且用反指標運算子(*it)來取得當前項的值。
多數還支援遞減運算子(--it / it--) 移動到前一項(稱作雙向迭代器)
有些支援隨機存取(random access), +/- 運算子(it+k / it-k) 一次能移動多項
對於一個容器,分別有代表頭尾的迭代器,其中頭代表首項,而尾代表最後一項的下一項,因此遍歷方式通常是for(it=begin;it!=end;it++)
對於陣列 (C-style array),其迭代器就是指標,且頭尾分別是 arr/arr+n,對於 STL 內的容器,其迭代器是C::iterator,且頭尾分別是arr.begin() / arr.end()
此外STL內還有實作反向迭代器用來從最後一項開始往前遍歷,其形態是 C::reverse_iterator 頭尾分別是 arr.rbegin() / arr.rend()
#include<vector>
動態陣列。有時候我們無法預估陣列所需要宣告的長度,又必須宣告陣列,這時會選用 vector
vector 的常用函式 : push_back , pop_back , size , empty , begin , end
clear, resize, assign
須注意的是若等到 vector 空間不夠裝,vector 會自動安排記憶體,但這麼做常數比較大,所以通常在使用前會先預設一些記憶體位置給 vector
vector.reserve(100005)
vector<int> vec(n)
insert, erase 複雜度為 \(O(n)\) ,請勿亂用
實際演練 : 常用函式
#include <iostream> #include <vector> // vector 的標題檔 using namespace std; int main() { vector<int> Mega; //宣告一個 vector 名稱叫 Mega 用來裝 int 元素 //裡面的容器是空的 for (int i=1; i<=5; i++) Mega.push_back(i); for (int i=5; i>=1; i--) Mega.push_back(i); cout << Mega.size() << "\n"; for (int i=1; i<=4; i++) Mega.pop_back();// 將 vector 尾端元素刪掉 cout << Mega.size() << "\n"; cout << Mega.empty() << "\n"; // empty() 函式為查詢 vector 裡面是否有元素 // 回傳值為 bool 1 裡面沒有元素 0 則是有 Mega.clear(); cout << Mega.empty() << "\n"; }
實際演練 : 使用 index 遍歷
#include <iostream> #include <vector> using namespace std; int main() { vector<char> roy; for (int i=0; i<26; i++) { char c = i + 'A'; roy.push_back(c); } for (int i=0; i<roy.size(); i++) { cout << roy[i] << " "; } cout << roy[5] << "\n"; cout << roy[0] << "\n"; // 查詢單一個元素可用陣列方式查詢 }
實際演練 : 遍歷 iterator 遍歷
#include <iostream> #include <vector> using namespace std; int main() { vector<int> vt; vector<int>::iterator iter; for (int i=95; i<=100; i++) { vt.push_back(i); } iter = vt.begin(); for(iter = vt.begin(); iter != vt.end(); iter++) { cout << *iter << "\n"; // 一定要 「 * 」 號 } }
#include<stack>
堆疊是一種先進後出的結構,像是疊盤子一樣,先放下去的會最慢被拿出來。
stack的常用函式 : emplace(push) , size , empty , top , pop .
不支援隨機存取:O(N) 查找速度慢:O(N) 在集合中增刪元素很快:O(1)
實際演練:
#include <iostream> #include <stack> // stack 的標題檔 using namespace std; int main() { stack<int> Stk; //宣告一個 stack 名稱叫 stk 用來裝int元素 //一開始裡面的容器是空的 for (int i=4; i<=10; i++) { Stk.push(i); } cout << Stk.top() << "\n"; // 輸出 stack 最上層的元素,所以會輸出 10 Stk.pop(); // 刪除 stack 最上層的元素 cout << Stk.top() << "\n"; // 輸出 stack 最上層的元素,所以會輸出 9 cout << "裡面有多少元素" <<Stk.size() << "\n"; // 查詢 stk 裡面有幾個元素,裡面元素有 4 5 6 7 8 9共六個,所以會輸出 6 cout << Stk.empty() << "\n"; // empty() 函式為查詢 stack 裡面是否有元素 // 回傳值為bool 1 裡面沒有元素 0 則是 //清除 stack的元素 while(Stk.size() != 0) { // 或是 while(Stk.empty == 0) Stk.pop(); } cout << "裡面有多少元素" << Stk.size() << "\n"; cout << Stk.empty() << "\n"; }
stack之括弧匹配
想法:
給予一個字串並包含了括號 '[', ']', '(', ')' 和 '{', '}',請驗證該字串中的括號是否合法配對。
也就是 "([])", "{()}", "[]" 為合法配對,但是 "(]", 或 "([)]" 就是不合法配對。
規律:
#include<queue>
佇列是一種先進先出的結構,像是水管一樣的單向道,最早進去的會最先通出來。
queue的常用函式 : push , size , empty , front , pop .
不支援隨機存取:O(N) 查找速度慢:O(N) 在集合中增刪元素很快:O(1)
常用在BFS
實際演練:
#include <iostream> #include <queue> // queue 的標題檔 using namespace std; int main() { queue<int> que; //宣告一個 queue 名稱叫 que 用來裝int元素 //一開始裡面的容器是空的 for (int i=4; i<=10; i++) { que.push(i); } cout << que.front() << "\n"; // 輸出 queue 最上層的元素,所以會輸出 4 que.pop(); // 刪除 queue 最上層的元素 cout << que.front() << "\n"; // 輸出 queue 最上層的元素,所以會輸出 5 cout << "裡面有多少元素" <<queue.size() << "\n"; // 查詢 queue 裡面有幾個元素,裡面元素有 5 6 7 8 9 10共六個,所以會輸出 6 cout << que.empty() << "\n"; // empty() 函式為查詢 queue 裡面是否有元素 // 回傳值為bool 1 裡面沒有元素 0 則是 //清除 queue的元素 while(que.size() != 0) { // 或是 while(que.empty == 0) que.pop(); } cout << "裡面有多少元素" << que.size() << "\n"; cout << que.empty() << "\n"; }
使用兩個 stack 去實作出 queue 的功能
作法:
始終維護 s1 作爲存儲空間,以 s2 作爲臨時緩衝區。
入隊時,將元素壓入 s1。
出隊時,將s1的元素逐個“倒入”(彈出並壓入)s2,將 s2 的頂元素彈出作爲出隊元素,之後再將 s2 剩下的元素逐個“倒回” s1。
可以很快很方便的加入元素 或 取出當前最優先的元素
常用於優化複雜度
在標頭檔 queue 裡面
priority_queue 裡面可以塞 int , string , long long , 實作原理是 max_heap 在運作
為一序列容器 是一個帶優先級的 queue
priority_queue 的常用函式 : push , size , empty , top , pop .
實際演練:
#include <queue> priority_queue<int> pq; priority_queue<int, vector<int>, greater<int> > pq1; //從小到大 變成min_heap pq.push(5); pq.push(8); pq.push(4); // 元素可重複,會個別保留 pq.push(5); cout << pq.size() << "\n"; cout << pq.top() << "\n"; pq.pop(); cout << pq.size() << "\n"; cout << pq.top() << "\n"; cout << pq.empty() << "\n";
對每一個輸入,請輸出到現在為止已輸入的數的中位數。
輸入: 輸出:
1 1
3 2
4 3
60 3
70 4
50 27
2 4
維護兩個 pq(priority_queue),
其中一個 pq 維護大於等於中位數的數值
另一個 pq 維護小於中位數的數值
如此一來,中位數必定為大於等中位數那堆的最小值
大於等中位數的 pq 每次取最小值
即為中位數
priority_queue<int, vector<int>, greater<int>> big;
cout << big.top() << endl;
判斷當前加入的新元素 x 如果小於中位數,則加入維護小的 pq
否則加入維護大的 pq
if(x < big.top()) small.push(x);
else big.push(x);
為了讓中位數固定在比較大的那堆的最小值,
兩堆 pq 的大小至多只能差一
因此每次新增完元素,
如果小的那堆太多要把最大的元素丟過去大的那堆
反過來則大的最小丟去小的那堆
priority_queue<int> small;
priority_queue<int, vector<int>, greater<int>> big;
while(small.size() >= big.size()) big.push(small.top()), small.pop();
while(small.size()+1 < big.size()) small.push(big.top()), big.pop();
#include<set>
set<int> s; // []
s.insert(1); // s = [1]
s.insert(5); // s = [1, 5]
s.insert(3); // s = [1, 3, 5]
s.insert(2); // s = [1, 2, 3, 5]
s.insert(1); // s = [1, 2, 3, 5]
s.erase(3); // s = [1,2,5]
set<pair<int,int> > s; //[]
s.insert({1, 2}); // [{1, 2}]
s.insert({2, 1}); // [{1, 2}, {2, 1}]
s.insert({2, 1}); // [{1, 2}, {2, 1}]
s.insert({3, 5}); // [{1, 2}, {2, 1}, {3, 5}]
s.count({2, 1}); //return 1,代表 {2, 1} 有在集合內;
s.count({5, 3}); //return 0
set<int> s; // []
s.insert(1); // s = [1]
s.insert(5); // s = [1, 5]
s.insert(3); // s = [1, 3, 5]
s.insert(2); // s = [1, 2, 3, 5]
cout << *s.begin() << endl; // 1
cout << *s.rbegin() << endl; // 5
如果沒找到則回傳 end()
set<int> s; // []
s.insert(1); // s = [1]
s.insert(5); // s = [1, 5]
s.insert(3); // s = [1, 3, 5]
s.insert(2); // s = [1, 2, 3, 5]
if(s.lower_bound(3) != s.end())
cout << s.lower_bound(3) << endl;
else
cout << "NOT FOUND" << endl;
如果沒找到則回傳 end()
set<int> s; // []
s.insert(1); // s = [1]
s.insert(5); // s = [1, 5]
s.insert(3); // s = [1, 3, 5]
s.insert(2); // s = [1, 2, 3, 5]
if(s.lower_bound(3) != s.end())
cout << s.upper_bound(3) << endl;
else
cout << "NOT FOUND" << endl;
#include<map>
映射,基本上就是多了個對應值的set,單位型態為 pair<key,value>
用法跟 set 很像,但多了 operator[] 可以取值或存值。
map<int, int> mp;
mp[3] = 1; //[3] = 1,
mp[2] = 2; //[2] = 2, [3] = 1;
mp[3] = 5; //[2] = 2, [3] = 5;
map<string,int> mp; // map 還可以用 string 當 key
mp["qwer"] = 1;
mp["abc"] = 2;
multiset<int> s; //[]
s.insert(1); // [1]
s.insert(2); // [1, 2]
s.insert(3); // [1, 2, 3]
s.insert(1); // [1, 1, 2, 3]
s.count(1); //return 2;
s.erase(1); //[2, 3]
使用 erase(x) 會把全部值為 x 的元素刪除
如果只要刪除一個可以使用 s.erase(s.find(x)) 刪除
multimap<int, string> mp; //[]
mp.insert({1, "c8763"});
mp.insert({1, "38763"});
mp.insert({2, "1234"});
mp.insert({4, "mpmp"});
mp.count(1); // return 2
#include<bitset>
可看作是 bool 陣列,支援位元運算,且成員函數多為位元相關函數,
用在位元運算常數非常小,
沒有要做位元運算別亂用(用 vector<bool> 就好)
但長度必須是常數
常用函式:count、size、test、any、none、all、set、reset、flip。
#include<bitset> //bitset
#include<iostream>
bitset<10> a = 7;
bitset<10> b(10);
bitset<10> c("10010");
int main(){
b = 5; //101
b >>= 1; //10
cout << b << endl;
cout << (a|c) << " " << (a&c) << " " << (a^c) << endl;
}
#include<list>
C++ 內建的 linked-list,可以在 O(1) erase, insert
但沒有支援隨機存取(random access), 無法使用 index 查詢
常用函數: push_back, push_front, pop_back, pop_front, insert, erase, size
#include<list>
#include<iostream>
using namespace std;
int main(){
list<int> arr;
arr.push_back(3); // [3]
arr.push_back(4); // [3, 4]
list<int>::iterator iter = prev(arr.end()); // 儲存 arr 最後一個元素的iterator
arr.push_back(1); // [3, 4, 1]
arr.push_front(10); // [10, 3, 4, 1]
arr.erase(iter); //[10, 3, 1]
}
input
8 2
1 5 8 3 6 4 7 2
3
4
output
1 2
[1 5 8 3 6 4 7 2] \(\to\) [1 5 4 7 2] \(\to\) [1 2]
暴力找某個元素的位置最差情況需要遍歷整個序列 \(O(n)\)
但太慢了
因此通常會搭配 map,把每個元素儲存在 list 中的位置儲存在 map 中
#include<list>
#include<iostream>
using namespace std;
int main(){
list<int> arr;
map<int, list<int>::iterator> mp;
int n; cin >> n;
for(int i = 0, x; i < n; i++){
cin >> x;
arr.push_back(x);
mp[x] = prev(arr.end());
}
}
移除操作 –- 把數字 x 以及左右相鄰的元素刪除
void remove(int x){ list<int>::iterator it = mp[x]; if(it != arr.begin()) arr.erase(prev(it)); if(next(it) != arr.end()) arr.erase(next(it)); arr.erase(it); }
map 維護儲存、查詢為 \(O(\log n)\),
\(q\) 筆操作總複雜度為 \(O(q\log n)\)