# 標準模板庫 # STL ---- ## 概要 STL常用容器與用法整理 搭配[基礎資結](https://hackmd.io/@PixelCat/SyRGa24pO)、[複雜度](https://hackmd.io/@PixelCat/S1ZVa3Eau)服用,風味更佳 --- ## STL是什麼 ---- ### S tandard ### T emplate ### L ibrary 根本沒解釋到 = =" ---- STL提供一些寫好的簡單常見資料結構 不管什麼型別的資料都能裝 本篇將介紹 1. 什麼是模板 (template關鍵字) 2. 如何用STL裝資料 (STL容器) 3. 如何從容器裡取出資料 (STL迭代器) 4. 關於仿函數 (函數物件) ---- 僅列出跟競賽相關和/或我有用過的內容 想當C++大師的同學請自行挖[文](https://cplusplus.com/reference/stl/)[件](https://cplusplus.com/reference/iterator/iterator/)或[google](https://www.google.com/search?q=c%2B%2B+STL) ---- 有些不常用或太不初學的內容也有寫出來 初學者看到[迭代器](#/15)或[仿函數](#/16)基本上可以跳過 那些都算進階用法了 --- ## template 關鍵字 ---- 假設我們想自己實作一個 `max` 函數 ```cpp= int myMax(int a, int b){ if(a > b) return a; else return b; } long long int myMax(long long int a, long long int b){ if(a > b) return a; else return b; } double myMax(double a, double b){ if(a > b) return a; else return b; } //..... ``` 型別這麼多,根本沒完沒了 可是他們都在做一樣的事情啊 ---- 模板函數的語法 ```cpp= template<typename T> T myMax(T a, T b){ if(a > b) return a; else return b; } int main(){ int a = myMax<int>(3, 4); //OK string b = myMax<double>(3.6, 2.5); //OK } ``` 我們創造出了萬能的 `max` 函數! ---- 同樣的想法也能用在結構體上 ```cpp= template<typename T1, typename T2> struct TwoInOne{ T1 var1; T2 var2; }; int main(){ TwoInOne<int, string> a; //OK a.var1 = 5; a.var2 = "owo"; TwoInOne<char, bool> b; //OK b.var1 = 'z'; b.var2 = true; } ``` 只要宣告的時候用角括號包起來 這個結構體可以裝任意型態的兩個變數 ---- template的精神在於 既然這個函數/物件對任何型別都有用 那**乾脆把型別也當成一個參數** 這樣就不用管型別的問題了 ---- > STL提供一些寫好的簡單常見資料 > 結構,不管什麼型別的資料都能裝 於是通常STL容器都會跟著角括號一起出現 表示STL什麼都可以裝 --- ## std::pair ---- 可以把兩個東西包在一起而不用自己寫一個struct 不算在STL,跟STL很像就一起講吧 ---- ### 宣告 角括號裡面分別放兩個東西的型別名稱 ```cpp= pair<int, int> p1; pair<string, char> p2; ``` ---- ### 建構 建構函數(constructor)也就是初始化函數(initializer) 決定這個物件一開始長怎樣 ```cpp= pair<int, int> p3(1, 2); //宣告同時指定初始值 ``` ---- ### 操作 `first`、`second` 分別存取兩個元素 ```cpp= pair<int, char> p(10, 'c'); p.second = 'a'; //第二個元素 cout << p.first << "," << p.second << "\n"; // 10,a ``` ---- ### 其他 pair有內建比較運算 先比first再比second 都一樣就一樣 ```cpp= pair<int, int> a(2, 4), b(3, 3); cout << (a > b) << "\n"; // 0 swap(a.first, a.second); cout << (a <= b) << "\n"; // 0 ``` ---- `make_pair` 製造一個新的pair ```cpp= pair<int, char> p; p = make_pair(5, 'c'); p = pair<int, char>(5, 'c'); ``` 兩行效果完全一樣 個人偏好問題 ~~教派戰起來,我要看到血流成河~~ ---- pair有個兄弟[tuple](https://en.cppreference.com/w/cpp/utility/tuple) 可以裝不只兩個東西 不過我是沒用過,偏好自己寫struct --- ## std::vector ---- 可變長度的陣列 vector直翻是向量,可是std::vector跟向量完全不像 \_( \_-w-)\_ ---- ### 宣告 角括號裡放你要裝的型別 ```cpp= std::vector<int> v1; //整數陣列 ``` ---- ### 建構 ```cpp= std::vector<int> v2(size); //初始長度 std::vector<int> v3(size, value); //初始長度、初始值 ``` ---- ### 操作 在尾端新增一個元素,陣列變長一格,$\mathcal{O}(1)$ ```cpp= v1.push_back(value); v1.emplace_back(value); ``` 從尾端刪除一個元素,陣列變短一格,$\mathcal{O}(1)$ ```cpp= v1.pop_back(); ``` 若pop時陣列為空,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 回傳目前陣列長度,$\mathcal{O}(1)$ ```cpp= v1.size() ``` 回傳目前陣列長度是否為零,$\mathcal{O}(1)$ ```cpp= v1.empty() ``` ---- ### 操作 把陣列清空(長度變成0),$\mathcal{O}(N)$ ```cpp= v1.clear(); ``` 重新指定陣列長度,$\mathcal{O}(N)$ ```cpp= v1.resize(newSize); ``` 複雜度中的 $size$ 代表目前陣列長度 $\Delta size$ 代表長度變化量 ---- ### 操作 取得第一個元素,$\mathcal{O}(1)$ ```cpp= v1.front() ``` 取得最後一個元素,$\mathcal{O}(1)$ ```cpp= v1.back() ``` 若此時陣列為空,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 中括號用法跟一般陣列一樣,$\mathcal{O}(1)$ ```cpp= v1[index] ``` 若index超出目前陣列大小,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 取得指向 `v1[0]` 的迭代器,$\mathcal{O}(1)$ ```cpp= v1.begin() ``` 取得指向 `v1[k]` 的迭代器,$\mathcal{O}(1)$ ```cpp= v1.begin() + k ``` 取得指向 `v1[v1.size()]` 的迭代器,$\mathcal{O}(1)$ ```cpp= v1.end() ``` ---- ### 其他 begin和end常用在[這些](https://hackmd.io/@nehs-iced-7th/rk7moqETd#/4/5)函數上 ```cpp= std::vector<int> v4 = {2,4,3,1}; // v: 2 4 3 1 sort(v4.begin(), v4.end()); // v: 1 2 3 4 auto it = upper_bound(v4.begin(), v4.end(), 2); // v: 1 2 3 4 // it: ^ cout << it - v4.begin() << "\n"; // 2 ``` --- ## std::deque ---- 雙端隊列 {++**d**++|}ouble {++**e**++|}nded {++**que**++|}ue 音近 _deck_ 簡單來說就是兩邊都能新增刪除的vector (正常的隊列 aka. queue 在後面) ---- ### 宣告 角括號裡放你要裝的型別 ```cpp= std::deque<int> dq1; //整數陣列 ``` ---- ### 建構 建構函數(constructor)也就是初始化函數(initializer) 決定這個物件一開始長怎樣 ```cpp= std::deque<int> dq2(size); //初始長度 std::deque<int> dq3(size, value); //初始長度、初始值 ``` ---- ### 操作 在尾端新增一個元素,陣列變長一格,$\mathcal{O}(1)$ ```cpp= dq1.push_back(value); dq1.emplace_back(value); ``` 從尾端刪除一個元素,陣列變短一格,$\mathcal{O}(1)$ ```cpp= dq1.pop_back(); ``` 若pop時陣列為空,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 在前端新增一個元素,陣列變長一格,$\mathcal{O}(1)$ ```cpp= dq1.push_front(value); dq1.emplace_front(value); ``` 從前端刪除一個元素,陣列變短一格,$\mathcal{O}(1)$ ```cpp= dq1.pop_front(); ``` 若pop時陣列為空,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 回傳目前陣列長度,$\mathcal{O}(1)$ ```cpp= dq1.size() ``` 回傳目前陣列長度是否為零,$\mathcal{O}(1)$ ```cpp= dq1.empty() ``` ---- ### 操作 把陣列清空(長度變成0),$\mathcal{O}(size)$ ```cpp= dq1.clear(); ``` 重新指定陣列長度,$\mathcal{O}(\Delta size)$ ```cpp= dq1.resize(newSize); ``` 複雜度中的 $size$ 代表目前陣列長度 $\Delta size$ 代表長度變化量 ---- ### 操作 取得第一個元素,$\mathcal{O}(1)$ ```cpp= dq1.front() ``` 取得最後一個元素,$\mathcal{O}(1)$ ```cpp= dq1.back() ``` 若此時陣列為空,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 中括號用法跟一般陣列一樣,$\mathcal{O}(1)$ ```cpp= dq1[index] ``` 若index超出目前陣列大小,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 取得指向 `dq1[0]` 的迭代器,$\mathcal{O}(1)$ ```cpp= dq1.begin() ``` 取得指向 `dq1[k]` 的迭代器,$\mathcal{O}(1)$ ```cpp= dq1.begin() + k ``` 取得指向 `dq1[dq1.size()]` 的迭代器,$\mathcal{O}(1)$ ```cpp= dq1.end() ``` ---- ### 其他 begin和end常用在[這些](https://hackmd.io/@nehs-iced-7th/rk7moqETd#/4/5)函數上 ```cpp= std::deque<int> dq4 = {2,4,3,1}; // v: 2 4 3 1 sort(dq4.begin(), dq4.end()); // v: 1 2 3 4 auto it = upper_bound(dq4.begin(), dq4.end(), 2); // v: 1 2 3 4 // it: ^ cout << it - dq4.begin() << "\n"; // 2 ``` ~~全部從vector那邊複製過來,好耶~~ ---- ### 其他 deque看起來可以完全取代vector 然而deque的執行速度比vector稍慢 在某些題目中可能會有<font color="#FC6">TLE</font>的風險 所以能用vector就用vector吧 --- ## std::list ## std::forward_list ---- `std::list` 是雙向連結串列 `std::forward_list` 是單向連結串列 ---- 兩個都沒人在用,跳過 --- ## std::queue ---- 正常的隊列 後面進去前面出來 所以叫FIFO (first in first out) 詳情左轉[基礎資結](https://hackmd.io/@nehs-iced-7th/SyRGa24pO) 可以完全被[deque](#/5)取代 ---- ### 宣告 角括號裡放你要裝的型別 ```cpp= std::queue<int> que; //整數隊列 ``` ---- ### 建構 我[一個都沒用過](https://cplusplus.com/reference/queue/queue/queue/) ---- ### 操作 新增元素排最後面,隊伍長度變長一格,$\mathcal{O}(1)$ ```cpp= que.push(value); que.emplace(value); ``` 砍掉最前面的元素,隊伍長度變短一格,$\mathcal{O}(1)$ ```cpp= que.pop(); ``` 假如都沒人了你還硬要砍,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 回傳目前隊伍長度,$\mathcal{O}(1)$ ```cpp= que.size() ``` 回傳目前隊伍是不是沒人,$\mathcal{O}(1)$ ```cpp= que.empty() ``` ---- ### 操作 回傳隊伍最前面的元素,$\mathcal{O}(1)$ ```cpp= que.front() ``` 假如隊伍現在沒人,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 其他 queue裡面的東西(例如排第二個是誰)是看不到的 要偷看請用[deque](#/5) ---- ### 其他 queue沒有自帶clear,要清空請這樣 ```cpp= while(!que.empty()) que.pop(); ``` --- ## std::stack ---- 堆疊 後面進去後面出來 所以叫LIFO (last in first out) 詳情左轉[基礎資結](https://hackmd.io/@nehs-iced-7th/SyRGa24pO) 可以完全被[vector](#/4)或[deque](#/5)取代 ---- ### 宣告 角括號裡放你要裝的型別 ```cpp= std::stack<int> stk; //整數堆疊 ``` ---- ### 建構 [一個都沒用過](https://cplusplus.com/reference/stack/stack/stack/) ---- ### 操作 新增元素排最後面,堆疊長度變長一格,$\mathcal{O}(1)$ ```cpp= stk.push(value); stk.emplace(value); ``` 砍掉最後面的元素,堆疊長度變短一格,$\mathcal{O}(1)$ ```cpp= stk.pop(); ``` 假如都沒人了你還硬要砍,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 回傳目前堆疊長度,$\mathcal{O}(1)$ ```cpp= stk.size() ``` 回傳目前堆疊是不是沒東西,$\mathcal{O}(1)$ ```cpp= stk.empty() ``` ---- ### 操作 回傳堆疊最後面的元素,$\mathcal{O}(1)$ ```cpp= stk.top() ``` 假如堆疊現在沒東西,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 其他 stack裡面的東西(例如排第二個是誰)是看不到的 要偷看請用[vector](#/4)或[deque](#/5) ---- ### 其他 stack沒有自帶clear,要清空請這樣 ```cpp= while(!stk.empty()) stk.pop(); ``` ~~又是從前面複製貼上,好耶~~ --- ## std::priority_queue ---- 優先隊列 每個元素丟進去之後在裡面比大小 最贏的最先被丟出來 也就是一個heap 詳情左轉[基礎資結](https://hackmd.io/@nehs-iced-7th/SyRGa24pO) 預設是越大的越先出來 內部實作使用小於運算子做比較 也可以自訂判斷條件 像[這樣](#/9/8)[這樣](#/16) ---- ### 宣告 角括號指定資料型別 ```cpp= std::priority_queue<int> pq1; //整數優先隊列 ``` ---- ### 操作 新增一個元素,$\mathcal{O}(\log (size))$ ```cpp= pq1.push(value); pq1.emplace(value); ``` 把容器裡最大的元素砍掉,$\mathcal{O}(\log (size))$ ```cpp= pq1.pop(); ``` 假如都沒人了你還硬要砍,還是{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} 複雜度中的size代表目前容器裡有多少元素 ---- ### 操作 回傳目前容器裡有幾個元素,$\mathcal{O}(1)$ ```cpp= pq1.size() ``` 回傳目前容器是否為空,$\mathcal{O}(1)$ ```cpp= pq1.empty() ``` ---- ### 操作 回傳容器裡最大的元素,$\mathcal{O}(1)$ ```cpp= pq1.top() ``` 假如容器現在是空的,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 其他 常見的寫法是 ```cpp= priority_queue<int, vector<int>, greater<int>> pq3; ``` 內建的仿函數 `greater<int>` 等同於 `operator >` 代表 `pq3` 中數字越小越早跑出來 這種優先隊列的用法非常常見 ---- ### 其他 priority_queue也沒有自帶clear,要清空請這樣 ```cpp= while(!pq1.empty()) pq1.pop(); ``` --- ## std::set ---- 沒有重複元素的集合 可以 1. 插入元素 2. 刪除元素 3. 查詢集合裡有沒有某元素 4. 集合裡自動對元素排序 ---- ### 宣告 角括號指定資料型別 ```cpp= std::set<int> s1; //整數集合 ``` ---- ### 操作 插入一個新元素,$\mathcal{O}(\log (size))$ ```cpp= s1.insert(value); s1.emplace(value); ``` 刪除一個元素,$\mathcal{O}(\log (size))$ ```cpp= s1.erase(value); //刪除value這個值 ``` 假如沒這個值給你刪,什麼都不會發生 ---- ### 操作 檢查一個值有沒有在集合裡出現,$\mathcal{O}(\log (size))$ ```cpp= s1.count(value) ``` ---- ### 操作 回傳目前容器裡有幾個值,$\mathcal{O}(1)$ ```cpp= s1.size() ``` 回傳目前容器是否為空,$\mathcal{O}(1)$ ```cpp= s1.empty() ``` 清空容器,$\mathcal{O}(size)$ ```cpp= s1.clear(); ``` ---- ### 操作 取得頭尾迭代器,$\mathcal{O}(1))$ ```cpp= s1.begin() s1.end() ``` :::spoiler 依照慣例 `end()` 指向的是虛無 我自己在測試的時候 `*s1.end()` 總是跟s1的大小相等 不知道是不是巧合 總之可以確定這個值不是s1裡的任何一個元素 ::: ---- ### 操作 尋找一個元素,$\mathcal{O}(\log (size))$ 假如找到了,回傳指向那個元素的迭代器 假如沒找到,回傳 `s1.end()` ```cpp= s1.find(value) ``` 也可以用迭代器刪除元素,$\mathcal{O}(1)$ ```cpp= s1.erase(iter); ``` ```cpp= s1.erase(value); //這兩行 s1.erase(s1.find(value)); //效果相同 ``` 假如你去erase一個指向虛無的迭代器,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 set裡面的元素是自動排序好的(由小到大) 所以可以[二分搜](https://hackmd.io/@nehs-iced-7th/S1bNH5YB_#/9)找東西? 可是直接用 `std::XXXer_bound` 的速度也是不行的 請用set自己的 `XXXer_bound` 才是$\mathcal{O}(\log (size))$ ```cpp= lower_bound(s1.begin(), s1.end(), value); //turtle speed s1.lower_bound(value); //speedyyy upper_bound(s1.begin(), s1.end(), value); //turtle speed s1.upper_bound(value); //speedyyy ``` ---- ### 其他 注意跟vector、deque不一樣 set的迭代器只能往前往後移 例如我們想指向set裡第k小的元素 這樣是不行的 ```cpp= auto iter = s1.begin() + k; //OAO ``` 要這樣 ```cpp= auto iter = s1.begin(); for(int i = 0; i < k; i++) iter++; ``` ---- ### 其他 迭代器不能加法,那可以減法嗎? :::spoiler 天底下哪有這麼好的事 {有人|我}還真的在比賽中試圖這麼做 想當然耳{他|我}失敗了 ::: ```cpp= // value 是第幾小? auto iter = s1.lower_bound(value); cout << (iter - s1.begin()) << "\n"; // error: no match for 'operator-' ``` ---- ## std::map ---- 直翻是映射 map裡面裝很多組 (key, value) 所有key滿足前面set的性質 效果類似一個索引值任意的萬能陣列 在內部每一組(key, value)以一個 `std::pair` 儲存 ---- <image src="https://i.imgur.com/a3tPode.png" height=500px></image> ---- ### 宣告 角括號指定key和value的型別 ```cpp= map<int, string> m1; //從整數映射到字串 ``` ---- ### 操作 插入一個pair,$\mathcal{O}(\log (size))$ p.first當key,p.second當value 假如key已經存在,什麼事都沒發生 ```cpp= m1.insert(p); //p is of type std::pair ``` 移除一個key(和他對應的value),$\mathcal{O}(\log (size))$ ```cpp= m1.erase(key); ``` 假如沒這個key給你刪,什麼都不會發生 ---- ### 操作 中括號可以用key找他對應的value,$\mathcal{O}(\log (size))$ ```cpp= m1.insert(make_pair(key1, value1)); cout << m1[key1] << "\n"; // value1 ``` 假如戳一個不存在的key,會自動建立一個新的 因此insert也可以不用這麼麻煩 ```cpp= m1.insert(make_pair(key2, value2)); m1[key2] = value2; //一樣意思,常用 ``` ---- ### 操作 檢查一個key是否存在,$\mathcal{O}(\log (size))$ ```cpp= m1.count(key) ``` ---- ### 操作 回傳目前容器裡有幾組(key, value),$\mathcal{O}(1)$ ```cpp= m1.size() ``` 回傳目前容器是否為空,$\mathcal{O}(1)$ ```cpp= m1.empty() ``` 清空容器,$\mathcal{O}(size)$ ```cpp= m1.clear(); ``` ---- ### 操作 取得頭尾迭代器,$\mathcal{O}(1))$ ```cpp= m1.begin() m1.end() ``` ---- ### 操作 用key尋找一對(key, value),$\mathcal{O}(\log (size))$ 假如找到了,回傳指向那個元素的迭代器 假如沒找到,回傳 `m1.end()` ```cpp= m1.find(key) ``` 也可以用迭代器刪除元素,$\mathcal{O}(1)$ ```cpp= m1.erase(iter); ``` ```cpp= m1.erase(key); //這兩行 m1.erase(find(key)); //效果相同 ``` 假如你去erase一個指向虛無的迭代器,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 跟set一樣有`lower_bound`、`upper_bound` 但是幾乎沒用過 反正set有什麼功能 同樣的事情基本上也可以用在map的key上面 ---- ### 其他 map的迭代器規則跟set[一模一樣](#/10/10) \_( \_-w-)\_ --- ## std::multiset ## std::multimap ---- 可以容納重複元素的set/map multiset有用 multimap從來沒用過所以不理他 ---- multiset大部分用法跟正常set一模一樣 唯一要注意的是erase會把所有一樣的值刪光光 只刪一個的話請用find ```cpp= multiset<int> ms; //... ms.erase(value); //erase all 'value' ms.erase(ms.find(value)); //erase one 'value' ``` ---- 剩下的自己看[前面](#/10)吧 --- ## std::bitset ---- 幾乎等同一個bool陣列 不太像STL,無家可歸所以在這裡一起寫 ---- 正常的bool理論上只需要1個位元 但是記憶體以 8位元 = 1 byte 為單位 所以正常的bool佔1 byte浪費了其他7倍的空間 bitset除了壓縮空間以外 還可以一次對很多位元做相同的操作 因此執行速度也比陣列快幾十倍 ---- ### 宣告 角括號裡是一個整數,代表陣列大小 (單位: 位元) 之後就不能再改大小了 ```cpp= std::bitset<64> bs; //64 bits ``` ---- ### 操作 中括號用法跟陣列一樣,$\mathcal{O}(1)$ ```cpp= bs[63] = 1; ``` 假如戳到bitset外面,{**++<font color="#f00">↑不↑</font><font color="#ff0">↓可↓</font><font color="#0f0">↑預↑</font><font color="#00f">↓期↓</font>++**|未定義行為} ---- ### 操作 各種你想得到的位元運算 包括 `&`、`|`、`^`、`~`、`<<`、`>>` `&=`、`|=`、`^=`、`>>=`、`<<=` 複雜度是 $\mathcal{O}(\dfrac{size}{B})$ B通常估個30~60左右 ---- ### 操作 還有其他運算 `==`、`!=` 輸入的`cin >> bs`、輸出的`cout << bs` ---- ### 操作 整個bitset全部設成0,可能是 $\mathcal{O}(\dfrac{size}{B})$ ```cpp= bs.reset(); ``` 算算看有多少個bit是1,可能是 $\mathcal{O}(\dfrac{size}{B})$ ```cpp= bs.count() ``` 根據實測count比reset稍微慢一些 --- ## 迭代器 iterator ---- 迭代器聽起來超炫炮 基本上就只是一個弱化的指標 容器可能會有奇怪的結構也不給你偷看裡面長怎樣,此時迭代器幫你存取容器裡的資料 ---- 每一種容器有各自的迭代器 名字都是 `容器型別::iterator` 例如 ```cpp= map<int, int>::iterator it; ``` 不想打字的我們更常用`auto`順便給初始值 ```cpp= auto it = m1.begin(); //must give a value with 'auto' ``` ---- 你可以(經常)對迭代器做的事有 1. `it--` 把迭代器往前移一格 2. `it++` 把迭代器往後移一格 3. `*it` 把it指向的東西抓出來 4. `it1 == it2`、`it1 != it2` 檢查兩個迭代器是不是指向同一個咚咚 ---- ### 情境(1): 把map裡面的東西輸出 前面提到map中存的是(key, value)的pair 所以map的迭代器指向的就是這個pair `it->first`等同於`(*it).first`是key `it->second`等同於`(*it).second`是value ```cpp= map<int, int> mp; //... auto it = mp.begin(); while(it != mp.end()){ cout << "(" << it->first << "," << it->second << ")\n"; it++; } ``` 不需要知道map裡面長怎樣,一路`++`過去就好了! ---- ### 情境(2): 把整個vector排序 [內建的`std::sort`](https://hackmd.io/@nehs-iced-7th/rk7moqETd#/4/5)可以把\[l,r)區間排序 正好\[begin, end)就是整個vector,所以 ```cpp= vector<int> v; //... sort(v.begin(), v.end()); ``` ---- ### 情境(3): 把set中,小於k的最大的值刪掉 還記得lower_bound指向>=k的第一個數字嗎? ```cpp= set<int> st; //... auto it=st.lower_bound(k); if(it==st.begin()){ //all >= k }else{ st.erase(prev(it,1)); } ``` `prev(it, n)`回傳it前面第n個迭代器 類似地`next(it, n)`是往後數第n個 ---- ### 情境(4): 輸出multiset中第一個比k大的數字 不用解釋了吧 ```cpp= multiset<int> ms; //... cout << *ms.upper_bound(k) << "\n"; ``` ---- 就這樣 迭代器在競賽中的使用很單純 他就是一個封裝好的指標,幫你忽略容器的結構,專心對付資料本身 ---- 現在可以解釋for auto了... ```cpp= set<int> st; //(1) for(auto i:st){ //... } //(2) auto it = st.begin(); for(; it != st.end(); it++){ auto i = *it; //... } ``` 兩種寫法是完全一樣的 也就是讓`i`跑過st裡的每個元素 --- ## 以上 ---- STL博大精深 這裡只寫出競賽常用的一小部分 ---- 第一次嘗試把這些語法的東西寫下來 可能摻雜了太多太冷僻的內容 假如看不懂就自己做實驗或直接跳過吧 語法進步總是靠觀摩實作堆起來的 多寫,多試,多查 多看別人寫的程式 都會比看十次我的講義還有用 ---- 最重要的 > 常用的自然會記起來 > 不常用的記了也沒用
{"metaMigratedAt":"2023-06-16T04:02:12.015Z","metaMigratedFrom":"YAML","title":"STL","breaks":true,"slideOptions":"{\"theme\":\"black\",\"transition\":\"slide\"}","description":"STL常用容器與用法整理搭配基礎資結、複雜度服用,風味更佳","contributors":"[{\"id\":\"f6c1f4c6-eaa6-44aa-a181-7ce7c40dc9d5\",\"add\":0,\"del\":22},{\"id\":\"25d6f561-7fc8-4c0b-8638-c8ad8a1c8b75\",\"add\":1,\"del\":3176},{\"id\":\"84d8099a-a721-4db6-8fe4-cfea2a2d82b4\",\"add\":48022,\"del\":27763}]"}
    1354 views
   Owned this note