# 資料結構 --- ### 什麼是資料結構(Data Structure)? ---- 資料結構是「組織資料的方式」,讓我們能更有效率地「儲存、查詢、修改、刪除」資料。 陣列就是其中一種。 --- ## STL標準函式庫 ### (Standard Template Library) ---- C++ 內建 (std 內) 的模板庫 有很多好用的資料結構 沒有的就需要手刻 ---- #### 需要手刻 : 1. BIT 2. Treap 3. Segment Tree 4. Sparse Table ... ---- 不過這些都不在APCS的考試範圍所以不會教 今天只會教最基礎的 --- ## 迭代器(iterator) 事先說明,詳細介紹在後面 ---- ### 甚麼是迭代(iteration) 「迭代」的核心概念是: 重複逐個存取資料結構中的每個元素,直到全部處理完畢 其實就是跑迴圈的意思 如下是一個最基本的迭代行為: ```cpp= int a[] = {1, 2, 3, 4}; for (int i = 0; i < 4; i++) { cout << a[i] << "\n"; } ``` ---- 而迭代器簡單來說就是指標的概念 用來指定某個元素(位址) 要取迭代器指的內容就加上取值運算子( * ) --- ## pair ---- ### 宣告 ```cpp= #include<utilit> //std::pair<型別1, 型別2> 名稱; std::pair<int, int> p; std::pair<int, int> p(1, 2); std::pair<int, int> p = {1, 2}; auto p = std::make_pair(1, 2); ``` 一次存兩個元素的資料結構 ---- ### 操作 ```cpp= std::pair<int, int> p; p.first = 1; p.second = 2; int a, b; std::tie(a, b) = p; // auto[a, b] = p; // a=1, b=2 std::pair<int,int> q = {30,40}; std::swap(p, q); // 交換內容 ``` ---- ### 比較大小 依字典序比較 也就是先比first,再比second ```cpp= pair<int, int> p1(2, 3), p2(2, 4); bool a = p1 < p2; // a = true ``` ---- ### 特殊用法 ```cpp= pair<pair<int, int>, int> p; p.frist.first = 1; p.first.second = 2; p.second = 3; ``` ---- ## 補充 ---- ## tuple (pair的多元素版本) ---- ### 宣告 ```cpp= #include <tuple> std::tuple<型別1, 型別2, ..., 型別N> 變數名稱; std::tuple<int, int, int> t; std::tuple<int, int, int> t(1, 2, 3); std::tuple<int, int, int> t = {1, 2, 3}; auto t = std::make_tuple(4, 5, 6); ``` ---- ### 操作 ```cpp= std::tuple<int, int, int> t; std::get<0> (t) = 4; std::get<1> (t) = 5; std::get<2> (t) = 6; int a, b, c; tie(a, b, c) = t; // auto [a, b , c] = t; // a = 4, b = 5, c = 6 ``` 更進階的用法請見chatGPT ---- ### 比較 | | pair | tuple | struct | |:-------- |:----:|:--------:|:--------:| | 元素數量 | 2 | 任意數量 | 任意數量 | | 成員名稱 | first, second | get<> | 自訂 | --- ## vector 動態陣列 ---- ### 宣告 ```cpp= #include<vector> // std::vector<型別> 名稱; vector<int> v; vector<int> v(n); // 長度為n,元素初始值為0 vector<int> v(n, x); // 長度為n,所有元素初始值為x vector<int> v2(v1); // 用v1複製一個新的vector vector<int> v = {1, 2, 3, 4}; vector<vector<int>> v(n, vector<int>(m)); // 建立一個n*m的陣列 ``` ---- ### 操作 ```cpp= vector<int> v; v.push_back(x); // 將x加入尾端 v.insert(v.begin()+i, x); // 在第i個位置前插入x v.insert(v.begin()+i, n, x); // 在第i個位置前插入n個x v.pop_back(); // 刪除最後一個元 int val = v[i]; // 取出第i個元素 v[i] = x; // 修改第i個值為x size_t n = v.size(); // 取得目前元素個數(v的大小) bool a = v.empty(); // 是否為空(回傳true/flase) v.clear(); // 清空 v.resize(n, x); // 改變長度為n,所有元素為x int val = v.front() // 回傳遞一個元素 int val = v.back() // 回傳最後一個元素 v.erase(v.begin()+i); // 刪除第i個元素 v.erase(v.begin()+l, v.begin()+r); // 刪除[l, r)區間 reverse(v.begin(), v.end()); // 反轉順序 find(v.begin(), v.end(), x); // 查找x出現的第一個位置 v1.swap(v2); // 交換兩個vector的內容 ``` ---- ### vector的迭代器 1. begin() 2. end() 3. rbegin() 4. rend() ---- ![](https://hackmd.io/_uploads/SyyEj2LZa.png) ---- ![](https://hackmd.io/_uploads/SJUos3LZT.png) ---- ### 迭代器宣告 ```cpp= vector<資料型態>::iterator 名稱; //begin(), end() vector<資料型態>::reserve_iterator 名稱; //rbegin, rend() ``` 通常都用auto ---- #### 示範 ```cpp= vector<int> v = {1, 2, 3, 4, 5}; auto it=v.begin(); auto rit=v.rbegin(); cout<<*it<<"\n"; //1 cout<<*rit<<"\n"; //5 //輸出v的全部元素 for (auto i=v.begin();i!=v.end();i++) cout<<*i<<" ";//法一 for (auto k:v) cout<<k<<" "; //法二 for (size_t i=0;i<v.size();i++) cout<<v[i]<<" "; //法三 ``` --- ## stack 堆疊 ---- 後進先出 (Last-In-First-Out, LIFO) ---- ![](https://hackmd.io/_uploads/rJmJf68b6.png) ---- ### 宣告 ```cpp= #include<stack> // std::stack<型別> 名稱; stack<int> stk; ``` ---- ### 操作 ```cpp= stack<int> stk; stk.push(x); // 把元素 x 放入 stk stk.pop(); // 移除最上層元素 int x = stk.top(); // 取最上層元素(不刪除) bool a = stk.empty(); // stk 是否為空 size_t n = s.size(); // 回傳 stack 大小 ``` (stack不支援迭代器) ---- ~~沒用的資料型態~~ ~~完全可以被vector取代~~ --- ### list / forward_list 鏈結串列 ---- list : 雙向鏈結串列(doubly linked list) forward_list : 單向鏈結串(singly linked list) ---- ### 宣告 ```cpp= #include <list> std::list<型別> 名稱; // 空 list std::list<型別> 名稱 = {a, b, c}; // 初始化 list #include <forward_list> std::forward_list<型別> 名稱; // 空 forward_list std::forward_list<型別> 名稱 = {a, b, c}; // 初始化 ``` ---- ### 操作 ```cpp= //共有操作 lis.push_front(x); // 在頭端插入元素 lis.pop_front(); // 刪除頭端元素 lis.sort(); // 排序 lis.reverse(); // 反轉 lis.unique(); // 刪除相鄰重複元素 lis.remove(x); // 刪除所有等於指定值的元素 lis.remove_if(); // 刪除所有符合條件的元素 // list獨有 list<int> lis; lis.push_back(x); // 尾端插入元素 lst.pop_back(); // 刪除尾端元素 lst.insert(it, x); // 在指定迭代器位置前插入元素x lst.erase(it); // 刪除指定迭代器元素 lst.merge(lst2); // 合併兩個已排序 list int n = lst.size();// 取得元素個數 lst.splice(pos, list& other); //將other整個list的元素搬到pos位置之前,other變成空的 lst.splice(pos, list& other, it); //將other中的迭代器it指向的單個元素搬到pos前,other只減少這個元素 lst.splice(pos, list& other, first, last); //將other中[first, last)範圍的元素搬到pos前,other中這些元素被移走 // forward_list獨有 forward_list<int> fl; fl.insert_after(it, x); // 在指定迭代器"後方"插入元素x fl.erase_after(it); // 刪除指定迭代器"後方"元素 fl.splice_after(pos, forward_list& other); //將other的所有元素搬到pos"後面",other變成空的 fl.splice_after(pos, forward_list& other, it); //將other的單個元素搬到pos"後面",other只減少這個元素 fl.splice_after(pos, forward_list& other, first, last); //將other的[first, last)範圍元素搬到pos"後面",other中這些元素被移走 ``` (list支援迭代器) --- ## queue 單向佇列 ---- 單向佇列 先進先出 (First-In-First-Out, FIFO) ---- ![](https://hackmd.io/_uploads/SyCNua8-p.png) ---- ### 宣告 ```cpp= #include<queue> //std::queue<型別> 名稱; queue<int> q; ``` ---- ### 操作 ```cpp= queue<int> q; q.push(x); // 將元素x加到q尾端 q.pop(); // 移除q頭端的元素 q.front(); // 取得q頭端元素(不移除) q.back(); // 取得q尾端元素(不移除) bool a = q.empty(); // q是否為空 size_t n = q.size(); // 回傳q內元素個數 ``` (不支援迭代器) --- ## deque 雙向佇列 ---- ![](https://hackmd.io/_uploads/rkTC9C8-6.png) ---- ### 宣告 ```cpp= #include<deque> //std::deque<型別> 名稱; deque<int> dq; ``` ---- ### 操作 ```cpp= deque<int> dq; dq.push_back(x); dp.push_front(x); dq.pop_back(); dq.pop_front(); int val = dq.back(); int val = dq.front(); size_t = dq.size(); bool a = dq.empty(); dq.insert(dq.begin()+i, x); dq.erase(dq.begin()+i); dq.clear(); ``` ---- ### deque的迭代器 1. begin() 2. end() 3. rbegin() 4. rend() --- ## priority_queue ---- 優先佇列 和queue很像 但會排序! 預設是取大的 通常用 __堆積(Heap)__ 來實作 ---- ![](https://hackmd.io/_uploads/ByMKE4q0gx.png) ---- ### 宣告 ```cpp= //std::priority_queue<型別, 底層容器, 比較函式> 名稱; #include<queue> #include<vector> #include<functional> //預設(最大堆) //std::priority_queue<型別, std::vector<型別>, std::less<型別>> 名稱; pirority_queue<int> pq; //最小堆 priority_queue<int, vector<int>, greater<int>> pq; ``` 底層容器只能是支援隨機存取的序列容器 e.g. vector, deque ---- ### 操作 ```cpp= priority_queue<int> pq; pq.push(x); pq.pop(); int val = pq.top(); bool a = pq.empty(); size_t = pq.size(); pq.swap(pq2); ``` ---- ## 補充 手刻比較函示 ---- ### 方法1 struct ```cpp= include<bits/stdc++.h> using namespace std; struct cmp { bool operator()(int a, int b) { return a > b; // 表示 a 應該排在 b 後面(最小堆) } } int main() { priority_queue<int, vector<int>, cmp> pq; pq.push(10); pq.push(5); pq.push(20); while(!pq.empty()) { cout << pq.top() << " "; // 5 10 20 pq.pop(); } } ``` ---- ### 方法2 lambda ```cpp= #include<bits/stdc++.h> using namspace std; int main() { auto cmp = [](int a, int b) {return a > b; }; priority_queue<int, vector<int>, decltype(cmp)> pq(cmp); pq.push(10); pq.push(5); pq.push(20); while(!pq.empty()) { cout << pq.top() << " "; // 5 10 20 pq.pop(); } } ``` ※ decltype (declare type)是用來 取得一個變數或表達式的「型別」 --- ## set / multiset / unordered_set ---- | 特性 | `set` | `multiset` | `unordered_set` | |:--------------------:|:----------------:|:----------------:|:---------------:| | 底層資料結構 | RB-tree | RB-tree | Hash table | | 元素順序 | 自動排序 | 自動排序 | 無序 | | 是否允許重複 | 否 | 是 | 否 | | 插入、查找、刪除效率 | O(log n) | O(log n) | 平均 O(1) | ---- ### 宣告 ```cpp= #include<set> #include <unordered_set> //std::set<型別, 比較函式(預設std::less<型別>)> 名稱; //std::multiset<型別, 比較函式(預設std::less<型別>)> 名稱; //std::unordered_set<型別, hash函式(預設std::hash<型別>), key_equal函式(預設std::equal_to<型別>)> 名稱; set<int> s; //大到小 set<int, greater<int>> s; ``` ※ unordered_set 裡面的型別別亂放 std::hash和只支援一些基礎的型別 今天教的資料型態除了pair都不支援 如果你依舊想放的話就要自己寫hash函式 ---- ### 操作 ```cpp= set<int> s; s.insert(x); s.erase(x); s.earse(s.begin()+i); s.find(x); s.size(); s.empty(); s.clear(); s.count(x); unordered_set<int> set; set.reserve(n); // 預留空間 ``` (支援迭代器) --- ## map / multimap / unordered_map ---- set的pair版 跟函數很像$(x, f(x))$ 就是鍵與值的概念 ---- ### 宣告 ```cpp= #include <map> #include <unordered_map> //std::map<鍵的型別, 值的型別, 比較函式(預設std::less<鍵的型別>)> 名稱; //std::multimap<鍵的型別, 值的型別, 比較函式(預設std::less<鍵的型別>)> 名稱; //std::unordered_map<鍵的型別, 值的型別, 哈希函式(預設std::hash<鍵型別>), 相等函式(預設std::equal_to<鍵型別>> 名稱; map<int, int> mp; map<int, int, greater<int>> mp; ``` ---- ### 操作 ```cpp= map<int, int> mp; mp.insert({key, value}); // 插入元素(若key已存在則不覆蓋) mp[key] = value; // 若key不存在則建立(multimap沒有這個) mp.erase(key); // 刪除鍵為 key 的元素 mp.find(key); // 回傳指向該key的iterator mp.count(key); // 傳該key出現次數 mp.clear(); // 清空整個容器 mp.empty(); // 檢查是否為空 mp.size(); // 目前元素數量 insert_or_assign(key, value); // 若存在就更新,不存在就插入(map專屬) unordered_map<int, int> m; m.reserve(N); // 預留空間 ``` BTW 取出 key 需要用.first 取出 value 需要用.second --- ## 迭代器 ---- ### 宣告 ```cpp= container_type<資料型態>::iterator 變數名稱; container_type<資料型態>::const_iterator 變數名稱; container_type<資料型態>::reverse_iterator 變數名稱; container_type<資料型態>::const_reverse_iterator 變數名稱; ``` ---- ### 迭代器函式 * begin() , cbegin() * end(), cend() * rbegin() , crbegin() * rend() , crend() 迭代器加上了 c,表示 const 的意思 也就是這些迭代器不能修改,只能讀取 ---- ### 基本操作(所有迭代器都有) | 操作 | 說明 | |:-----------:|:--------------------------:| | `*it` | 取得迭代器指向的元素值 | | `it->` | 取得迭代器指向的元素的成員 | | `++it` | 移動到下一個元素 | | `it == it2` | 比較迭代器是否相等 | ---- ### 實用函數 * `advance(it, n)` -- 讓迭代器 it 向前移動 n (可負)個位置 * `distance(first, last)` -- 計算兩個迭代器之間的距離(即元素個數) * `next(it, n = 1)` -- 不改變原迭代器,回傳「向後 n 個」的新迭代器。 * `prev(it, n = 1)` -- 不改變原迭代器,回傳「向前 n 個」的新迭代器。 ※ 往前的話不能是單向的迭代器 ---- ### 迭代器種類 分五種: (能力是遞增的,越下面的能做的事越多) 1. Input Iterator(輸入迭代器) 2. Output Iterator(輸出迭代器) 3. Forward Iterators(前向迭代器) 4. Bidirectional Iterators(雙向迭代器) 5. Random Access Iterators(隨機存取迭代器) ---- 「輸入/輸出迭代器」最基本,分別只能讀或寫 支援的容器有 istream_iterator, ostream_iterator ---- 「前向迭代器」是輸入輸出迭代器的升級版,可多次遍歷 支援的容器有 unordered_map, unordered_set ---- 「雙向迭代器」在多了反向移動(–)能力 支援的容器有 list, map, set, multimap, multiset ---- 「隨機存取迭代器」可支援像陣列一樣的隨機存取,操作與指標最為相似,效率最高 支援的容器有 vector, deque, array, string ---- ## 補充 ~~這個我只是從GPT抄來的~~ ~~所以不要問我 問了我也不會~~ ---- ### 迭代器適配器(Iterator Adaptors) 迭代器適配器是用來「包裝容器」,讓原本不能用迭代器輸入/輸出的操作變得可行。 它們是特殊的迭代器物件,用起來像一般迭代器,但行為不同(例如自動插入元素、從輸入流讀資料等)。 ---- #### 一、輸入輸出流迭代器(Stream Iterators) 常與 std::copy 一起使用 ```cpp= #include <iostream> #include <iterator> #include <vector> #include <algorithm> using namespace std; int main() { /* 語法: std::istream_iterator<T> it(輸入流); std::ostream_iterator<T> it(輸出流, "分隔符"); */ vector<int> v; // 從輸入讀到 EOF copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(v)); cout << "你輸入的數字是:"; copy(v.begin(), v.end(), ostream_iterator<int>(cout, " ")); } ``` ---- #### 二、插入迭代器(Insert Iterators) 這組讓 STL 演算法在執行「寫入」時 自動使用容器的 insert()、push_back() 或 push_front() ```cpp= #include <vector> #include <iterator> #include <algorithm> #include <iostream> using namespace std; int main() { /* 1. std::back_inserter(container) 適用:vector, deque, list */ vector<int> a = {1, 2, 3}; vector<int> b = {4, 5, 6}; // 自動 push_back copy(b.begin(), b.end(), back_inserter(a)); for (int x : a) cout << x << " "; // 輸出:1 2 3 4 5 6 /* 2. std::front_inserter(container) 適用:list, deque */ list<int> c = {1, 2, 3}; list<int> d = {4, 5, 6}; // 插入前端 copy(c.begin(), c.end(), front_inserter(d)); for (int x : c) cout << x << " "; // 輸出:6 5 4 1 2 3 /* 3. std::inserter(container, pos) 適用:list, vector, set, map 等有 insert 函數的容器 */ vector<int> e = {1, 2, 3}; vector<int> f = {9, 8, 7}; // 在第二個元素前插入 copy(f.begin(), f.end(), inserter(e, e.begin() + 1)); for (int x : e) cout << x << " "; // 輸出:1 9 8 7 2 3 } ``` --- ## 題目 ---- ### pair #### [Zerojudge n368](https://zerojudge.tw/ShowProblem?problemid=n368) ---- ### vector #### [MDCPP T014](http://mdcpp.mingdao.edu.tw/problem/T014) #### [MDCPP B015](http://mdcpp.mingdao.edu.tw/problem/B015) #### [Leetcode 1929](https://leetcode.com/problems/concatenation-of-array/description/) ---- ### stack #### [MDCPP B005](http://mdcpp.mingdao.edu.tw/problem/B005) #### [MDCPP D055](http://mdcpp.mingdao.edu.tw/problem/D055) #### [Zerojudge c123](https://zerojudge.tw/ShowProblem?problemid=c123) #### [Zerojudge f698](https://zerojudge.tw/ShowProblem?problemid=f698) #### [~~CSES Round Trip II~~](https://cses.fi/problemset/task/1678) #### [~~Zerojudge 真假子圖 (回滾併查集)~~](https://zerojudge.tw/ShowProblem?problemid=g598) ---- ### list #### [Zerojudge b586](https://zerojudge.tw/ShowProblem?problemid=b586) #### [Zerojudge i426](https://zerojudge.tw/ShowProblem?problemid=i426) #### [Zerrojudge n371. 5](https://zerojudge.tw/ShowProblem?problemid=n371) ---- ### queue #### [MDCPP B002](http://mdcpp.mingdao.edu.tw/problem/B002) #### [Leetcode 225](https://leetcode.com/problems/implement-stack-using-queues/description/) ---- ### deque #### [Zerojudge i400](https://zerojudge.tw/ShowProblem?problemid=i400) #### [TIOJ 1618 . 城市景觀問題](https://tioj.ck.tp.edu.tw/problems/1618) #### [TIOJ 1566 . 簡單易懂的現代都市](https://tioj.ck.tp.edu.tw/problems/1566) #### [~~Leetcode Jump Game II(01BFS)~~](https://leetcode.com/problems/jump-game-ii/description/) ---- ### priority_queue #### [MDCPP AP013](http://mdcpp.mingdao.edu.tw/problem/AP013) #### [Zerojudge d221](https://zerojudge.tw/ShowProblem?problemid=d221) #### [CSES Stick Divisions (霍夫曼編碼)](https://cses.fi/problemset/task/1161/) #### [~~CSES Investigation(dijkstra)~~](https://cses.fi/problemset/task/1202) ---- ### set #### [Zerojudge n369](https://zerojudge.tw/ShowProblem?problemid=n369) #### [MDCPP T016](http://mdcpp.mingdao.edu.tw/problem/T016) #### [CSES Movie Festival II (貪心)](https://cses.fi/problemset/task/1632) ---- ### map #### [Zerojudge b050](https://zerojudge.tw/ShowProblem?problemid=b050) #### [Zerojudge d244](https://zerojudge.tw/ShowProblem?problemid=d244) #### [CF Least Cost Bracket Sequence (反悔貪心)](https://codeforces.com/contest/3/problem/D) #### [~~CSES Meet in the Middle~~](https://cses.fi/problemset/task/1628) --- ## 補充 --- ## std::ranges ---- C++ 20 開始用來改進和取代舊的 STL 演算法 ( 現在Zerojudge不能用:< ) ---- ### 一、改良版的STL演算法 **特色:** 不必寫 .begin() / .end() ---- ### 常用演算法 * ranges::sort — 排序 * ranges::find — 找尋元素 * ranges::for_each — 對每個元素執行操作 * ranges::copy — 複製範圍 * ranges::min_element / ranges::max_element — 找最小或最大值 ---- ### 範例 ```cpp= #include <bits/stdc++.h> #include <ranges> //建議 using namespace std; int main() { vector<int> v = {5, 3, 1, 4, 2, 6, 7, 8}; // sort ranges::sort(v); // 直接排序 cout << "排序後: "; for (int x : v) cout << x << " "; cout << "\n"; // find auto it_find = ranges::find(v, 4); // 找到 4 if (it_find != v.end()) cout << "找到元素 4\n"; // for_each cout << "每個元素加 1: "; ranges::for_each(v, [](int &x){ x += 1; }); for (int x : v) cout << x << " "; cout << "\n"; // 4. min_element / max_element auto it_min = ranges::min_element(v); auto it_max = ranges::max_element(v); cout << "最小值: " << *it_min << ", 最大值: " << *it_max << "\n"; // 5. copy vector<int> copy_v; ranges::copy(v, back_inserter(copy_v)); cout << "複製後的資料: "; for (int x : copy_v) cout << x << " "; cout << "\n"; } ``` ---- ### 二、延遲運算的「視圖」 ``std::views`` 是 ranges 系統最強大的部分, 它提供「不複製資料」的操作,稱作 lazy evaluation(延遲求值)。 **特色:** * 懶惰求值:不會產生新容器,只在迭代時才計算。 * 管線操作:用 | 把多個 view 串起來,類似 Unix 管線。 * 支援多種轉換與篩選。 ---- ### 常用 `views` * views::filter - 篩選符合條件的元素 * views::transform - 對元素做轉換 * views::take - 取前 n 個元素 * views::drop - 忽略前 n 個元素 * views::iota - 生成連續數列 ---- ### 範例 ```cpp= #include <bits/stdc++.h> #include <ranges> //建議 using namespace std; int main() { vector<int> v = {5, 3, 1, 4, 2, 6, 7, 8}; // 篩選偶數 auto evens = v | views::filter([](int x){ return x % 2 == 0; }); // {4, 2, 6, 8} // 平方 auto squares = evens | views::transform([](int x){ return x*x; }); // {16, 4, 36, 64} // 取前 3 個 auto first_three = squares | views::take(3); cout << "偶數平方取前三: "; for (int x : first_three) cout << x << " "; cout << "\n"; // iota 生成數列 cout << "iota 1~9: "; for (int x : views::iota(1, 10)) cout << x << " "; cout << "\n"; // drop 前兩個 cout << "drop 前兩個元素: "; for (int x : v | views::drop(2)) cout << x << " "; cout << "\n"; } ``` ---- ### concept — 範圍型別檢查 * C++20 引入了 concepts,可以在編譯期檢查型別是否符合某種需求 * 範圍的定義:可以用 begin() 和 end() 遍歷。 * ``std::ranges::range<T>``:檢查型別 T 是否是一個「range」(可遍歷的物件)。 * ``std::ranges::range<T>`` 本身是一個 編譯期常數 (constexpr bool) ---- ### 範例 ```cpp= #include<bits/stdc++.h> #include <ranges> // 建議 using namespace std; int main() { vector<int> v = {1,2,3}; if constexpr (ranges::range<decltype(v)>) { cout << "v is a range!" << endl; } int x = 42; if constexpr (!ranges::range<decltype(x)>) { cout << "x is NOT a range!" << endl; } } ``` --- ## rope / pbds::tree ---- ### rope `rope` 是 GNU 的擴充資料結構,底層以平衡樹實作 對於大量字串的插入、刪除操作,比 `std::string` 更有效率 ---- | 操作 | `std::string` | `rope` | |:---------------:|:-------------:|:----------------:| | 取得第 i 個字元 | $O(1)$ | $O(log n)$ | | 插入字串 | $O(n)$ | $O(log n)$ | | 刪除字串 | $O(n)$ | $O(log n)$ | | 拼接字串 | $O(n)$ | $O(log n)$ | | 儲存空間 | 緊密陣列 | 稍微浪費指標空間 | ---- ### 宣告 ```cpp #include <ext/rope> //<bits/extc++.h> using namespace __gnu_cxx; crope r; // rope<char> r; ``` ---- ### 用法 ```cpp crope r, a, b; a += "u"; b += "vw"; r = a + b; // "uvw" r.push_back('d'); // "uvwd" r.append("e"); // "uvwde" cout << r[0]; // 'u' cout << r.size(); // 5 cout << r.empty(); // false r.erase(0, 1); // "vwde" erase(位置, 長度) r.insert(0, "k"); // "kvwde" insert(位置, 字串) r[0] = 'c'; // "cvwde" r.replace(0, 3, "abc"); // "abcde" replace(位置, 長度, 字串) crope s = r.substr(1,3);// "bcd" substr(位置, 長度) string s = r.c_str(); // 轉成 std::string ``` 注意:單引號識字元,雙引號是字串 有些不能混用 ---- ### pbds::tree 其實就是強化版的`set` 他多了兩個操作 操作的時間複雜度跟 `set` 一樣都是$O(logn)$ 但常數比較大 ---- ### 宣告 ```cpp #include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/tree_policy.hpp> // #include <bits/extc++.h> using namespace __gnu_pbds; tree< int, // key type null_type, // mapped type (set 類型) less<int>, // comparator rb_tree_tag, // 紅黑樹 tree_order_statistics_node_update // 支援 order statistics > tr; ``` ---- ### 基本操作 | 操作 | 說明 | 範例 | | ----------- | -------------- | ------------------------------ | | `insert(x)` | 插入元素 x | `s.insert(5);` | | `erase(x)` | 刪除元素 x | `s.erase(5);` | | `find(x)` | 查找元素 x | `if(s.find(5) != s.end()) ...` | | `count(x)` | 是否存在元素 x (0/1) | `s.count(5);` | | `size()` | 元素數量 | `s.size();` | | `empty()` | 是否為空 | `s.empty();` | ---- ### 特有操作 | 操作 | 說明 | | ------------------ | ------------------------------------------ | | `order_of_key(x)` | 返回 **小於 x 的元素個數** | | `find_by_order(k)` | 返回 **第 k 小元素** 的迭代器(從 0 開始數) |
{"title":"資料結構","description":"資料結構是「組織資料的方式」,讓我們能更有效率地「儲存、查詢、修改、刪除」資料。陣列就是其中一種。","contributors":"[{\"id\":\"0fedbf87-2d4f-488d-a09d-9555c0b2844c\",\"add\":23257,\"del\":2153,\"latestUpdatedAt\":1768217016572}]"}
    227 views