# 完整STL `第10週社課` ## 先備知識 不懂的請一定要問(~~&nbsp;&nbsp;結果真的變成訓練自學能力了QQ&nbsp;&nbsp;~~) 指標的取值:http://kaiching.org/pydoing/cpp-guide/unit-4-pointer-and-reference.html ### 隨機存取 如果一個資料結構可以直接取第幾項的值,則稱它為可隨機存取(random access), 例如陣列,要取第幾項都可以。 否則它就是循序存取(sequential access), 對於目前所在的那一項,只能移動到與它相鄰的那一項。 ## 目錄 點擊可以直接跳到該項目 * <a href="##關於上次的補充" style="color: black; ">關於上次的補充</a> * <a href="##stack" style="color: black; ">stack</a> * <a href="##queue" style="color: black; ">queue</a> * <a href="##deque" style="color: black; ">deque</a> * <a href="##priority-queue" style="color: black; ">priority queue</a> * <a href="##set" style="color: black; ">set</a> * <a href="##multiset" style="color: black; ">multiset</a> * <a href="##map" style="color: black; ">map</a> * <a href="##multimap" style="color: black; ">multimap</a> * <a href="##bitset" style="color: black; ">bitset</a> * <a href="##iterator" style="color: black; ">iterator</a> ## 關於上次的補充 ### push_back 如果你想要push_back一個pair到vector中, 千萬不要這樣做: ```cpp vector<pair<int, int>> v; v.pb(1,2); // 編譯錯誤 ``` 你應該: ```cpp vector<pair<int, int>> v; v.pb({1,2}); // 方法1 v.pb(make_pair(1,2)); // 方法2 ``` 因為make_pair函式會自動把int轉成long long、float轉成double(由編譯器判斷變數類型), 就可能導致程式出錯, 所以我們通常都是用大括號的寫法(對tuple一樣有用),而且也比較方便。 ### size() 觀察以下程式的輸出: ```cpp #include <bits/stdc++.h> using namespace std; int main() { vector<int> v; cout << v.size() << endl; cout << v.size() -1 << endl; } ``` 為什麼不是輸出-1呢? 因為v.size()的回傳值型態是unsigned int!(對所有STL資料結構都一樣) 所以減一會造成溢位!! ## stack ### 功能 堆疊,就像堆盤子一樣,從上方放盤子,取盤子也是從上方取, 因此後放的盤子會在先放的盤子的上面,具有後進先出的性質(LIFO,Last in First Out)。 ### 宣告 ``` stack<type> St; ``` type : 內容物型別 St : stack名稱 ### 使用 ```cpp // 取得頂端元素 St.top() // 加入元素到頂端 St.push(x); // 刪除頂端元素 St.pop(); // 取得大小(元素個數) St.size() // 回傳是否為空(bool) St.empty() ``` ### 迴圈遍歷 基本上沒有,只能邊取值邊刪值 ```cpp while(!St.empty()){ cout << St.top() << " "; St.pop(); } ``` 因為永遠只能知道頂端的元素,因此為不可隨機存取 ### 例題 * <a href="https://zerojudge.tw/ShowProblem?problemid=c123">ZJ c123</a> * <a href="https://zerojudge.tw/ShowProblem?problemid=a565">ZJ a565</a> * <a href="https://zerojudge.tw/ShowProblem?problemid=a813">ZJ a813</a> * <a href="https://codeforces.com/problemset/problem/1428/C">CF 1428C</a> ## queue ### 功能 佇列(隊列),就像排隊一樣,先排的人先出去,具有先進先出的性質(FIFO,First in First Out)。 實際上來說,就是先塞入的數值會先被取出(使用)。 ### 宣告 ``` queue<type> Q; ``` type : 內容物型別 Q : queue名稱 ### 使用 ```cpp // 取得前端元素 Q.front() // 從後方加入元素 Q.push(x); // 刪除前端元素 Q.pop(); // 取得大小(元素個數) Q.size() // 回傳是否為空(bool) Q.empty() ``` ### 迴圈遍歷 基本上沒有,只能邊取值邊刪值 ```cpp while(!Q.empty()){ cout << Q.front() << " "; Q.pop(); } ``` 因為永遠只能知道前端的元素,因此為不可隨機存取 ### 例題 * <a href="https://zerojudge.tw/ShowProblem?problemid=c249">ZJ c249</a> * <a href="https://zerojudge.tw/ShowProblem?problemid=e447">ZJ e447</a> ## deque ### 功能 雙端佇列(double-ended queue),就是能夠從兩邊推入數值與取值的queue(以取名來說是這樣,但功能不只如此),實際能做到的是是雙向vector(可隨機存取), 差別:如果從vector前方加入數值,會需要$O(n)$的操作,但deque是$O(1)$。 ### 宣告 ``` deque<type> dq; ``` type : 內容物型別 dq : deque名稱 ### 使用 ```cpp // 取得前端元素 dq.front() // 從前方加入元素 dq.push_front(x); // 刪除前端元素 dq.pop_front(); // 取得後端元素 dq.back() // 從後方加入元素 dq.push_back(x); // 刪除後端元素 dq.pop_back(); // 取得大小(元素個數) dq.size() // 回傳是否為空(bool) dq.empty() // 清除整個deque dq.clear(); // 在deque中索引值為i的值(用法同一般陣列) dq[i] ``` 基本上vector有的函式deque都有,可以直接把它看成是功能更多的vector(但相對占用記憶體較大) ### 迴圈遍歷 如vector,不在此贅述 ## priority queue ### 功能 優先佇列,就是能夠維持最大(小)值的堆(heap), 每次取出(讀取)的值都是整個堆中的最大(小)值, 和queue, stack不同,為了維護最大(小)值,插入和刪除操作都需要$O(\log n)$的時間,$n$為priority queue的大小。 ### 宣告 ``` priority_queue<type, con, cmp> pq; ``` type : 內容物型別 pq : priority queue名稱 con : 實作的容器種類,通常填入vector&#60;type&#62; cmp : 比較函式,跟sort函數一樣可以自定義 ### 使用 ```cpp // 取得大小(元素個數) pq.size() // O(1) // 取得最大(小)的元素 pq.top() // O(1) // 插入元素 pq.push(x); // O(log n) // 刪除最大(小)的元素 pq.pop(); // O(log n) ``` 如果忘記取出的順序,可以現場實驗 ```cpp // 優先取出最大值 priority_queue<int, vector<int>, less<int> > pq1; // 優先取出最小值 priority_queue<int, vector<int>, greater<int> > pq2; ``` ### 自定義cmp 這段程式的cmp函式會先把first由小到大排,然後再把second由大到小排。 ```cpp #include <bits/stdc++.h> #define loop(i,a,b) for(int i=a;i<b;i++) #define pii pair<int,int> #define ff first #define ss second #define pb push_back #define debug cout << "test\n"; using namespace std; struct cmp{ bool operator()(const pii &a, const pii &b){ return (a.ff > b.ff) || (a.ff > b.ff && a.ss < b.ss); } }; int main(){ int n; pii tmp; cin >> n; priority_queue<pii, vector<pii>, cmp> pq; loop(i,0,n){ cin >> tmp.ff >> tmp.ss; pq.push(tmp); } loop(i,0,n){ cout << pq.top().ff << "," << pq.top().ss << endl; pq.pop(); } } ``` ### 迴圈遍歷 基本上沒有,只能邊取值邊刪值 ```cpp while(!pq.empty()){ cout << pq.top() << " "; pq.pop(); } ``` ### 例題 * <a href="https://zerojudge.tw/ShowProblem?problemid=b606">ZJ b606</a> * <a href="https://zerojudge.tw/ShowProblem?problemid=d221">ZJ d221(同一題)</a> ## set C++以紅黑樹(有興趣可以自己去查)實作 ### 功能 顧名思義,用來維護一個元素是否出現過,因此保證同一個元素不會重複出現, 插入元素與查詢元素的複雜度都是$O(\log n)$,$n$為set的大小。 ### 宣告 ``` set<type> S; ``` type : 內容物型別 S : set名稱 ### 使用 ```cpp // 取得元素x的指標,若不存在則返回S.end() S.find(x); // O(log n) // 加入元素x S.insert(x); // O(log n) // 回傳是否為空(bool) S.empty() // O(1) // 清除整個set S.clear(); // O(n) // 取得大小(元素個數) S.size() // O(1) // 回傳set中出現x的次數(只會是0或1) S.count(x) // O(log n) // 刪掉該指標指向的位置的值 S.erase(set<type>::iterator itr); // O(1) // 刪掉一個元素 S.erase(x); // O(log n) // 刪掉介於區間[first, last)的元素(O(distance(first, last))) S.erase(set<type>::iterator first, set<type>::iterator last); // 回傳set中第一個大於等於x的元素的指標,如果沒有則回傳S.end() S.lower_bound(x) // O(log n) // 回傳set中第一個大於x的元素的指標,如果沒有則回傳S.end() S.uppper_bound(x) // O(log n) ``` lower_bound和upper_bound函數會在之後有更深入的介紹 ### 迴圈遍歷 #### STL 正向(必由小到大) ```cpp for (auto i = S.begin(); i != S.end(); i++) { cout << *i << " "; } ``` 反向(必由大到小) ```cpp for (auto i = S.rbegin(); i != S.rend(); i++) { cout << *i << " "; } ``` #### auto 由小到大 ```cpp for (auto &i : S) { cout << i << " "; } ``` ## multiset ### 功能 可以有多個元素重複的set,通常用來當作是插入、刪除後仍然維持排序的資料結構。 ### 宣告 ``` multiset<type> ms; ``` type : 內容物型別 ms : multiset名稱 ### 使用 大部分的功能和set相同,但有一些差別: ```cpp // 取得「其中一個」元素x的指標,若不存在則返回S.end() ms.find(x); // 刪掉「所有」元素x ms.erase(x); // 刪掉該指標指向的位置的值 ms.erase(set<type>::iterator itr); // O(1) // 回傳multiset中出現x的次數 ms.count(x) ``` 如果只想刪掉其中一個元素x,可以這樣寫: ```cpp auto it = ms.find(x); if(it != ms.end()) ms.erase(it); ``` ### 迴圈遍歷 同set,不再贅述 ## map C++以紅黑樹實作,內部以pair來儲存鍵值(key)與資料(value), 實際上只是一種特殊的set,以pair的第一項來進行搜尋。 ### 功能 類似python 的dict,可以想像成一個單映射函數, 也就是一個鍵值(key)對應一個資料(value),因此鍵值絕不重複 改值、取值都是$O(\log n)$時間,$n$是map的大小。 ### 宣告 ``` map<type1, type2> mp; ``` type1 : 鍵值型別 type2 : 對應到的資料型別 mp : map名稱 ### 使用 ```cpp // 使x(type1)對應到y(type2) mp[x] = y; // 取得x對應到的值,如果原本不存在對應值就把它設為0 mp[x] // 刪除x和其對應的值(y) mp.erase(x); // 清空整個map mp.clear(); // 回傳鍵值的指標,如果鍵值x不存在,則回傳mp.end(),可用來判斷一個鍵值是否存在對應值 auto it = mp.find(x); if(it == mp.end()) cout << "There is no value corresponded by key " << x << ".\n"; else cout << "mp[" << x << "] is " << it->second; ``` 註:<code>it-&#62;second</code> 相當於 <code>(*it).second</code> ### 迴圈遍歷 依序遍歷到的值必定是鍵值由小到大,且型別是pair&#60;type1, type2&#62; ```cpp for(auto &i:st){ cout << i << "\n"; } ``` 或是 ```cpp for(auto i = st.begin(); i != st.end(); i++){ cout << *i << "\n"; } ``` ## multimap C++以紅黑樹實作 ### 功能 類似python 的set ### 宣告 ``` multimap<type1, type2> mp; ``` type12 : 內容物型別 mp : multimap名稱 ### 使用 因為multimap支援一對多的功能,因此它不存在用<code>[]</code>取值、改值的功能。 ```cpp // 刪除x和其所有的對應的值(y) ms.erase(x); // 刪掉該指標指向的位置的值 ms.erase(multimap<type>::iterator itr); // 清空整個map ms.clear(); // 回傳鍵值的指標,如果鍵值x不存在,則回傳ms.end(),可用來判斷一個鍵值是否存在對應值 auto it = ms.find(x); if(it == ms.end()) cout << "There is no value corresponded by key " << x << ".\n"; else cout << "ms[" << x << "] is " << it->second; // 回傳multimap中出現鍵值x的次數 ms.count(x) ``` ## bitset 同vector&#060;bool&#062;一樣有位元儲存的性質,但是整體常數更佳,而且多神奇的功能。 缺點:初始化時不可以用變數設定它的大小 ```cpp const int N = 50; // declare bitset<N> bs, a, b, c; // set value bs = 10; // 0101000...0000 bs[10] = 1; bs[3] = 0; cout << bs.to_string(); cout << bs.to_ullong(); a = 10, b = 25; c = a + b; cout << c.to_ullong(); ``` 小技巧:可以直接拿來輸出變數的二進位表示值 ```cpp int x = -1; bitset<32> bs1(x); cout << bs1 << "\n"; float y = 0.3; bitset<32> bs2(*(int*)&y); cout << bs2 << "\n"; ``` ## iterator 迭代器,是指標的一種,指向的位置有你的元素,很像傳統的指標陣列的指標,有著非常廣泛的用途,有待讀者自行使用發現 有分以下幾種iterator 1. 隨機存取迭代器:支援+,-,++,-- (vector, deque) 2. 循序存取迭代器:支援++,-- (set, map) ```cpp vector<int> a(20); int b[20]; vector<int>::iterator itr = a.begin() + 7; for (int i = 0; i < 20; ++i) a[i] = b[i] = i; cout << *(++itr) << *(b + 8); ``` <!-- 20060705sean@gmail.com --> <!-- 顏色字體: --> <!-- <span style="color:#0000FF"> str <span> -->