---
tags: 成大高階競技程式設計 2021
---
# 2021 Week2 - C++
註::bulb: 代表相對少見、較進階或與主題關聯性較低的內容,讀者可自行斟酌觀看。
---
本篇將介紹一些 C++ 的常見用法,尤其是 [STL](#標準模板庫(Standard-Template-Library)) 的部分。
---
# 演算法效率(Algorithmic efficiency)
> 演算法與資料結構息息相關,一同影響著時間與空間的效率。
## 複雜度(computational complexity)
競程題目規模通常很大(e.g., $10^5$),我們更在意的是複雜度,
而 2 倍、3 倍、甚至 10 倍的常數倍優化通常不是重點。
:::warning
**大 O 符號(Big O notation)**:
$f(n)=O(g(n)){\;\iff\;}\exists\;k\gt 0,n_0,
\forall\,n\ge n_0
{\;\;}s.t.{\;\;}f(n)\le kg(n)$
:::
意思是說當 $n$ 足夠大時,存在一個正數 $k$,使得 $kg(x)$ 大於 $f(x)$,
所以在 $n$ 夠大的情況下,計算所需資源(時間、空間)不會超過 $g(n)$ 的量級。
> 透過一個較簡單的函數 $g(n)$,為 $f(n)$ **提供一個上界**。
:::spoiler 範例
假設一個程式碼片段如下,
```cpp!
int mx{v[0]}, mn{v[0]};
for (int i{1}; i < n; ++i)
if (v[i] > mx) mx = v[i];
for (int i{1}; i < n; ++i)
if (v[i] < mn) mn = v[i];
```
簡單估計,二個變數宣告加上二個迴圈,則 $f(n)=2+2n=O(n)$。
:::
<br>
常見的複雜度:
$O(1){\;\le\;}O(\log{n}){\;\le\;}O(n){\;\le\;}O(n\log{n}){\;\le\;}O(n^k){\;\le\;}O(k^n){\;\le\;}O(n!){\;\le\;}O(n^n)$
> k 為大於 $1$ 的常數
<hr class="dashed">
## 合理的複雜度
> 無特別強調時,複雜度通常是在說時間複雜度,但空間複雜度一樣需要考慮。
### 時間複雜度
時間限制以秒為單位(e.g., $1$ 秒、$3$ 秒、$10$ 秒),
但我們會考慮題目規模與複雜度,估計出一個計算量,
通常 $1$ 秒不超過 $10^7$(只是大約的範圍)。
> 假設設計的演算法複雜度為 $O(n\log_2{n})$,而 $n\le10^5$,
> 計算量就大約為 $2\times10^6$,足以通過時間限制。
<hr class="dotted">
### 空間複雜度
空間複雜度比較不會有問題,但還是需要確認一下,影響的因素主要就是變數與遞迴深度。
<hr class="dotted">
### 均攤分析(Amortized Analysis):bulb:
> **Amortized analysis differs from average-case analysis in that probability is not involved;
> an amortized analysis guarantees the average performance of each operation in worst case.**[^CLRS]
這邊透過最淺顯易懂,並能具象化表現其概念的記帳法(Accounting method),
來分析 [`std::vector`](#stdvector) 的效率。
#### `push_back()` 的行為
> 為了簡化分析,將 `vector` 的操作侷限於 `push_back()`,
> 在空間不足時,會將配置的記憶體擴增成兩倍。
如果還有剩餘空間,直接把新元素放入尾端;
如果沒有剩餘空間,則配置一個兩倍大小的空間,
將所有元素移動過去,再將新元素放入尾端。
#### 開始分析
> 令 $n$ 為目前元素個數,每次進行 `push_back()` 後,$n$ 變為 $n+1$。
當還有剩餘空間時,操作花費 $3$ 個硬幣,其中花費「$1$ 個硬幣放入新元素」,
剩下 $2$ 個硬幣把它儲存在這個位置;
等到沒有剩餘空間的時候,這次操作花費 $4$ 個硬幣,
先用「$1$ 個硬幣配置新的空間」,接下來可以發現,
區間 $[\frac{n}{2}:n)$ 的每個位置都存有 $2$ 個硬幣,
總共有 $n$ 個硬幣,這些「預支的硬幣剛好足以移動所有元素」,
接著用「$1$ 個硬幣放入新元素」,剩下二個硬幣一樣存起來。
每次 `push_back()` 只會花費 $3$ 或 $4$ 個硬幣,所以**均攤成本(amortized cost)為 $O(1)$**。
> 硬幣常用術語應為 credits。
**均攤複雜度不需特別看待,可視為其真實花費。**
---
# 基本型態(Fundamental types)
* `bool`
* 常量(Literal):`true`, `false`
* `char`
* 常量(Literal):`'c'`
* `int`
* 常量(Literal):`0`
* 範圍:$x\;{\le}\;2^{31}-1{\;\approx\;}2.14{\times}10^{9}$
* `long long`
* 常量(Literal):`0ll`, `0LL`
* 範圍:$x{\;\le\;}2^{63}-1{\;\approx\;}9.22{\times}10^{18}$
> `int` 與 `long long` 需要注意整數溢位(integer overflow)的問題。
* `float`
* 常量(Literal):`0.f`
* 範圍:共 6 位精確度
* `double`
* 常量(Literal):`0.`, `0.0`, `2e9` ($2\times10^9$)
* 範圍:共 15 位精確度
> double 與 float 的精確度取決**整數位數+小數位數**,
> 關於浮點數(floating-point number)的更多說明,請參閱 [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754)。
---
# 輸入輸出(Input/Output)
## [`std::cin`](https://en.cppreference.com/w/cpp/io/cin) / [`std::cout`](https://en.cppreference.com/w/cpp/io/cout)
C++ 的標準輸入輸出流,不需要型態,會根據變數自動判別。
`cin` 可以轉為 `bool`,用來判斷輸入是否出現狀況,例如遇到 EOF (End-of-file) 等等。
```cpp!
int x;
while (std::cin >> x) {
// 一直讀取整數直到 EOF
std::cout << "cin got " << x << std::endl;
}
```
<hr class="dashed">
## fast I/O
[`std::ios_base::sync_with_stdio()`
](https://en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio) 決定 C++ 的標準輸入輸出流(`std::cin, std::cout, ...`),
是否與 C 的標準輸入輸出流(`stdin, stdout, ...`,也就是 `scanf()/printf()`)同步。
在**關閉後,不可與 `scanf()/printf()` 混用**。
```cpp!
std::ios_base::sync_with_stdio(false);
```
`cin` 在建構時,會與 `cout` 綁定,
也就是 `cin` 在進行讀取前,會呼叫 `cout.flush()`,
在競程這是不必要的,還會增加執行時間。
> 輸入輸出頻繁的題目,可能導致 TLE。
```cpp!
std::cin.tie(nullptr);
```
所以在競程中,我們通常會加上這兩行,提升 `cin/cout` 的效率。
<hr class="dotted">
## [`std::endl`](https://en.cppreference.com/w/cpp/io/manip/endl)
除了換行還會呼叫 `os.flush();`,**濫用**會導致效能低落;
一般使用 `'\n'` 較佳。
<hr class="dotted">
## [fixed](https://en.cppreference.com/w/cpp/io/manip/fixed) & [setprecision](https://en.cppreference.com/w/cpp/io/manip/setprecision)
透過 `fixed` 與 `setprecision` 可以控制要輸出的小數點位數。
```cpp!
double x{1. / 3.};
os << fixed << setprecision(10) << x << '\n'; // 輸出到小數點下第 10 位
```
> os 是一個 `std::ostream` 的物件(e.g., `cout`)。
---
# 初始化(Initialization):bulb:
> **Uniform and general initialization using {}-lists**[^TC++PL]
C++ 核心指南(C++ Core Guidelines)中也提到 [ES.23:Prefer the {}-initializer syntax](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-list)。
* `T object { arg1, arg2, ... };`
* `function({ arg1, arg2, ... });`
* `return {arg1, arg2, ... };`
更多用法請參考 [List initialization](https://en.cppreference.com/w/cpp/language/list_initialization)。
---
# 標準函數庫(Standard Library)
標準函數庫內的東西均定義在 `std` 這個命名空間(namespace)中,
`using namespace std;` 可將所有 `std` 中的東西引入到當前的命名空間,
,通常是全域命名空間(global namespace),下方程式片段不再加上 `std::`。
除了介紹一些重要的基本概念外,以下內容著重在一些常見的使用方式,
完整的內容請讀者自行查看文件。
> 有些內容是 C\+\+14 或 C\+\+17 標準後才推出的,請注意版本上的問題。
> (g++ 編譯參數可使用 `-std=c++17`)
推薦的參考網站有 [C++ reference](https://en.cppreference.com/w/)、[cplusplus.com](http://www.cplusplus.com/)。
> 絕大部分涉及區間的操作,區間表示一律為「**左閉右開**」[^INTERVAL]。
<hr class="dashed">
## [標準模板庫(Standard Template Library](https://en.wikipedia.org/wiki/Standard_Template_Library))
標準模板庫是一個 C++ 的函數庫,
透過模板(template)實現泛型編程([Generic programming](https://en.wikipedia.org/wiki/Generic_programming)),
使得一份程式碼能夠與不同型態一起使用,
主要由演算法(Algorithms)、容器(Containers)、
函數(Functions)、迭代器(Iterators)四個部分組成,
並非標準函數庫的一部分。
不過它對於標準函數庫影響深遠,現在也已經加入了幾乎一樣的內容,
所以此處的 **STL 代指標準函數庫中任何與模板有關的部分**。
<hr class="dashed">
## 函數(Functions):bulb:
#### [`<functional>`](https://en.cppreference.com/w/cpp/header/functional)
> A function object is any **object** for which **the function call operator, operator(), is defined.**[^cppFO]
函數物件(function object)是一個擁有函數呼叫運算子(`operator()`)的物件,
可以像函數一樣使用。
函數物件的好處:
* 雖然函數跟函數物件都有類型,
但函數物件的類型可以對應到特定功能的函數物件,
而函數的類型只與參數與回傳值的型態有關,
所以函數物件透過模板,即可決定特定的功能
* 函數物件本質還是物件,可以攜帶狀態(透過資料成員(member data))
競程使用的機率不高,較常用的是 [`std::greater`](https://en.cppreference.com/w/cpp/utility/functional/greater)。
> **範例參見 [`std::priority_queue`](#stdpriority_queue)**。
:::spoiler 自己寫函數物件
下方是一個函數物件,回傳第幾次被呼叫。
```cpp!
struct myFunctor {
int t{0};
int operator()() {
return ++t;
}
};
```
```cpp!
myFunctor func1{}, func2{};
for (int i{0}; i < 10; ++i) cout << func1() << ' ';
// < 1 2 3 ... 10
cout << func2();
// < 1
```
:::
<hr class="thin">
### [匿名函數(Lambda expressions)](https://en.cppreference.com/w/cpp/language/lambda):bulb::bulb:
匿名函數(Lambda expressions)是一個函數物件,
大致分為三個部分,其中參數(`params`)與函數主體(`body`)與一般函數一樣,
下方僅針對 `captures` 做說明。
`[ captures ] ( params ) { body }`
`captures` 中宣告哪些變數可以被存取,主要分為二種,
capture by reference 與 capture by copy。
* capture by reference
* `&var`
* `&`:全部變數
* capture by copy
* `var`
* `=`:全部變數
* 預設為 `const`,加上 `mutable` 宣告字可改變
:::spoiler 範例
```cpp!
int a, b, c;
[](){};
// no capture
[&b](){};
// b captured by reference
[c](){};
// c captured by copy, const
[c]() mutable {};
// c captured by copy
[&](){};
// a, b, c captured by reference
[&, a](){};
// b, c captured by reference
// a captured by copy, const
[=](){};
// a, b, c captured by copy
[=, &a](){};
// b, c captured by copy, const
// a captured by reference
```
:::
<hr class="dashed">
## [迭代器(Iterators)](https://en.cppreference.com/w/cpp/iterator)
#### [`<iterator>`](https://en.cppreference.com/w/cpp/header/iterator)
迭代器與容器有很大的關聯,不同容器的資料結構並不相同,
如何進行操作是一大難題,所以迭代器為此提供一個共同的介面。
> 可以用指標(pointer)的概念去理解迭代器,某些迭代器可能就真的是指標,
> 不過迭代器大多是一個類別(class),透過運算子重載(operator overloading),
> 實現了很多與指標類似的操作。
迭代器之間是有分級的(hierarchical),可以簡單地分為三類,
* 單向迭代器
* 可以進行遞增(`++`)(incrementable)
* 在競程中很少見
* 雙向迭代器
* 可以進行遞增(`++`)(incrementable)
* 可以進行遞減(`--`)(decrementable)
* 隨機存取迭代器
* 可以進行常數時間的移動(`+`, `-`, ...)
迭代器的類型(type)跟容器 C 有關,為 `C::iterator`,
寫起來較為繁瑣,一般會用 [`auto`](https://en.cppreference.com/w/cpp/language/auto) 自動判別(e.g., `auto it{c.begin()};`)。
<hr class="thin">
* `next(it);`
* 回傳 `it` 的下一個迭代器
* `prev(it);`
* 回傳 `it` 的前一個迭代器(`it` 需為雙向迭代器)
* `advance(it, n);`
* 將 `it` 的前進 `n` 次
* 若 `n` 為負數,`it` 需為雙向迭代器
* `distance(it, it2);`
* 回傳 `it` 到 `it2` 的距離
* 若 `it` 為隨機存取迭代器,`it2` 可以在 `it` 前面
<hr class="dashed">
## [容器(Containers)](https://en.cppreference.com/w/cpp/container)
> A Container is an **object used to store other objects.**[^cppCtr]
容器是用來儲存其他資料的物件,
可以分為三個類型,循序容器(Sequence containers)、
關聯容器(Associative containers)、無序關聯容器(Unordered associative containers),
另外還有容器改寫器(Container adaptors)。
不同容器提供的操作可能相同,但複雜度卻會有所差異,
請盡量**了解容器內部的實作原理**,即可知道其複雜度。
<hr class="dotted">
### 共通函數
> 一些函數成員是大多數容器都有的。
* `c.begin();`
* 容器開頭的迭代器
* `c.end();`
* 容器結尾下一個位置的迭代器(an iterator to the element following the last element)
* `c.empty();`
* 容器是否為空
* `c.size();`
* 元素個數
* `c.clear();`[^clear]
* 清空容器
* `c.swap(c2);` / `swap(c, c2);`
* 交換二個容器(大多為常數時間)
容器因為都有 `begin()` / `end()`,可透過 [Range-based for loop](https://en.cppreference.com/w/cpp/language/range-for) 來遍歷所有元素。
> **push 相關函數,幾乎都有 emplace 版本替代**,
> 參數由 T 改為 T 的建構子(constructor)參數即可。
> `c.push({arg1, arg2, ...});` $\to$ `c.emplace(arg1, arg2, ...);`
<hr class="dotted">
### 循序容器(Sequence containers)
> 可以被循序(sequentially)存取的資料結構
<hr class="gradient">
#### [`<vector>`](https://en.cppreference.com/w/cpp/header/vector)
<hr class="thin">
### [`std::vector`](https://en.cppreference.com/w/cpp/container/vector)
`std::vector` 為動態配置陣列(dynamic allocated array),
儲存空間會自動動態擴增,使用上與一般的陣列(C-style array)很像。
```graphviz
digraph {
rankdir=LR;
vector [style=rounded, shape=box, fixedsize=true, width=0.8, height=0.5];
memory [shape=none, label=
<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
<TR>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="15" HEIGHT="15"></TD>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="15" HEIGHT="15"></TD>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="15" HEIGHT="15"></TD>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="15" HEIGHT="15"></TD>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="15" HEIGHT="15"></TD>
<TD COLSPAN="1" WIDTH="15" HEIGHT="15"></TD>
<TD COLSPAN="1" WIDTH="15" HEIGHT="15"></TD>
<TD COLSPAN="1" WIDTH="15" HEIGHT="15"></TD>
</TR>
</TABLE>>
];
vector :s -> memory :w;
}
```
常見用法:
* 建構子(constructor)
* `vector<T> v;` / `vector<T> v{};`
* 空的 `vector`
* `vector<T> v(n);`
* 有 `n` 個元素的 `vector`
* `vector<T> v(n, val);`
* 有 `n` 個元素的 `vector`,且元素的值為 `val`
* `v.resize(n);`
* 把 `vector` 的大小調整為 `n`
* `v.assign(n, val);`
* 把 `vector` 的內容改為 `n` 個元素,且元素的值為 `val`
* `v.push_back(val);`
* 將 `val` 插入尾端
* `v.pop_back();`
* 刪除尾端元素
`vector<int> v{10}` 與 `vector<int> v{10, -1}` 分別會得到長度為 1 與 2 的 `vector`,
**使用 `vector` 時,請改用 `()`。**
```cpp!
vector<int> v(5, 0);
// 5 個 0
v.assign(10, -1);
// 10 個 -1
for (int i{0}; i < 10; ++i) v[i] = i + 1;
// 1 2 3 4 5 6 7 8 9 10
v.pop_back();
v.pop_back();
// 1 2 3 4 5 6 7 8
v.resize(6);
// 1 2 3 4 5 6
v.push_back(10);
// 1 2 3 4 5 6 10
for (auto& x : v) cout << x << ' ';
cout << endl;
// 輸出所有元素
```
<hr class="gradient">
#### [`<array>`](https://en.cppreference.com/w/cpp/header/array)
<hr class="thin">
### [`std::array`](https://en.cppreference.com/w/cpp/container/array):bulb:
`std::array` 只是將固定大小的陣列封裝成一個類別(class),並且提供一些簡單的函數。
```graphviz
digraph {
subgraph cluster {
style=rounded;
margin=5.0;
label="array";
memory [shape=none, label=
<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
<TR>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="20" HEIGHT="20"></TD>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="20" HEIGHT="20"></TD>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="20" HEIGHT="20"></TD>
<TD COLSPAN="1" BGCOLOR="Gray" WIDTH="20" HEIGHT="20"></TD>
</TR>
</TABLE>>
];
}
}
```
用法:
* 建構子(constructor)
* `array<T, N> a;` / `array<T, N> a{};`
* 一個長度為 `N` 的 `array`,`N` 必須為定值
* `a.fill(val);`
* 將 `a` 填滿 `val`
假設需要一個 `vector`,每個元素固定為 4 個長整數。
```cpp!
vector<array<long long, 4>> v(10);
// 有 10 個長度固定為 4 的 array
for (int i{0}; i < v.size(); ++i) v[i].fill(i);
```
<hr class="gradient">
#### [`<list>`](https://en.cppreference.com/w/cpp/header/list)
<hr class="thin">
### [`std::list`](https://en.cppreference.com/w/cpp/container/list):bulb:
`std::list` 為雙向鏈結串列(doubly-linked list)。
```graphviz
digraph {
rankdir=LR;
node [style=rounded, shape=record, fixedsize=true, width=1, height=0.3,
label="{<prev>|data|<next>}"];
n0 :ne -> n1 :nw;
n1 :sw -> n0 :se;
n1 :ne -> n2 :nw;
n2 :sw -> n1 :se;
}
```
常見用法:
* 建構子(constructor)
* `list<T> lst;` / `list<T> lst{};`
* `lst.push_front(val);` / `lst.push_back(val);`
* 在前端(尾端)插入 `val`
* `lst.insert(pos, val);`
* 在 `pos` 前插入 `val`(`pos` 是一個迭代器)
* `lst.pop_front();` / `lst.pop_back();`
* 刪除前端(尾端)元素
* `lst.erase(pos);`
* 刪除 `pos` 位置的元素(`pos` 是一個迭代器)
```cpp!
list<int> lst{};
lst.push_front(1);
lst.push_front(2);
lst.push_front(3);
// 3 2 1
auto it{lst.begin()};
// it 指向 3
advance(it, 2);
// it 為雙向迭代器,不能進行 +/-
// it 指向 1
lst.insert(it, 4);
// 3 2 4 1
```
<hr class="gradient">
#### [`<deque>`](https://en.cppreference.com/w/cpp/header/deque)
<hr class="thin">
### [`std::deque`](https://en.cppreference.com/w/cpp/container/deque):bulb::bulb:
雙端佇列(Double-ended queue)是種抽象資料結構,可以進行頭尾的插入移除;
`std::deque` 是雙端佇列的實作,**支援常數時間的頭尾操作**,但遠遠不如此。
<hr class="thin">
內部結構通常由一堆大小固定的陣列(這邊簡稱**區塊**)組成,
另外有一個**紀錄陣列**,指向這一堆區塊。
每次在擴增記憶體時,會把紀錄陣列變大二倍,
並將目前的區塊移動到紀錄陣列的**中間**,使得紀錄陣列前後,
都有空間加入新的區塊,透過均攤分析,頭尾插入複雜度為 $O(1)$,
因為動到的是區塊在記錄陣列中的位置,不像 `vector` 需要移動每一個元素,常數較小。
其餘使用與 `vector` 相似,複雜度也大多相同,但常數大小有所差異,
像是索引(`operator[]`)較慢,因為要經過紀錄陣列,再到區塊中的某個位置。
```graphviz
digraph {
rankdir=LR;
subgraph cluster {
style=rounded;
label="deque";
map [shape=record, fixedsize=true, width=1, height=1,
label="|<c0>chunk 0|<c1>chunk 1|<c2>chunk 2|"];
begin [shape=box, label="begin()"];
end [shape=box, label="end()"];
}
c0 [shape=record, fixedsize=true, width=0.5, height=1,
label="<0>|<1>|<2>[0]|<3>[1]"];
c1 [shape=record, fixedsize=true, width=0.5, height=1,
label="<0>[2]|<1>[3]|<2>[4]|<3>[5]"];
c2 [shape=record, fixedsize=true, width=0.5, height=1,
label="<0>[6]|<1>|<2>|<3>"];
map:c0 -> c0:n;
map:c1 -> c1:n;
map:c2 -> c2:n;
begin -> c0:2;
end -> c2:1;
}
```
[^DQ]
<hr class="dotted">
### 關聯容器(Associative containers)
> 排序好的資料結構,提供 $O(\log n)$ 的搜尋複雜度,一般以紅黑樹(red-black tree),
> 一種平衡的二元搜尋樹(balanced binary search tree),來實作。
```graphviz
digraph {
splines=false;
nodesep=0.8;
node [style=dashed, shape=record, fixedsize=true, width=0.5, height=0.5]
n13 [label="{13|{<left>|<right>}}"];
n7 [label="{7|{<left>|<right>}}"];
n2 [label="{2|{<left>|<right>}}"];
n6 [label="{6|{<left>|<right>}}"];
n12 [label="{12|{<left>|<right>}}"];
n20 [label="{20|{<left>|<right>}}"];
n16 [label="{16|{<left>|<right>}}"];
n27 [label="{27|{<left>|<right>}}"];
n24 [label="{24|{<left>|<right>}}"];
n23 [label="{23|{<left>|<right>}}"];
n31 [label="{31|{<left>|<right>}}"];
edge [style=dashed, arrowhead=o];
n13:left -> n7;
n7 :left -> n2;
n2 :right-> n6;
n7 :right -> n12;
n13:right -> n20;
n20:left -> n16;
n20:right -> n27;
n27:left -> n24;
n24:left -> n23;
n27:right -> n31;
}
```
<hr class="gradient">
#### [`<set>`](https://en.cppreference.com/w/cpp/header/set)
<hr class="thin">
### [`std::set`](https://en.cppreference.com/w/cpp/container/set)
`std::set` 是一個集合,元素不能重複。
常見用法:
* 建構子(constructor)
* `set<T> s;` / `set<T> s{};`
* `s.insert(k);`
* 插入 `k`
* `s.erase(k);`
* 刪除 `k`
* `s.find(k);`
* 尋找等於 `k` 的元素,回傳此元素的迭代器(若不存在則回傳 `s.end()`)
* `s.count(k);`
* 回傳等於 `k` 的元素個數(0 或 1,因為元素不能重複)
* `s.lower_bound(k);`
* 尋找第一個不小於 `k` 的元素
* `s.upper_bound(k);`
* 尋找第一個大於 `k` 的元素
```cpp!
set<char> s{};
s.insert('s');
s.insert('o');
s.insert('r');
s.insert('t');
// 's' 'o' 'r' 't'
if (s.find('?') == s.end()) s.insert('?');
// 's' 'o' 'r' 't' '?'
auto it{s.lower_bound('a')};
// it 指向 'o'
for (auto& x : s) cout << x;
cout << endl;
// 輸出所有元素
// < ?orst
```
`set` 是由小到大排序好的,
因此可用 **`s.begin()`,`prev(s.end())`(`s.rbegin()`)**,
找到集合中**最小與最大值**。
<hr class="thin">
#### [`std::multiset`](https://en.cppreference.com/w/cpp/container/multiset)
`std::multiset` 與 `set` 幾乎一樣,差別只在元素可以重複。
**`erase(k)` 會刪除所有等於 `k` 的元素,**
如果只要刪除一個,請用刪除某個迭代器的方式進行。
```cpp!
s.erase(s.find(k));
```
<hr class="gradient">
#### [`<map>`](https://en.cppreference.com/w/cpp/header/map)
<hr class="thin">
### [`std::map`](https://en.cppreference.com/w/cpp/container/map)
`std::map` 是一個字典,
儲存鍵-值對(key-value pairs),鍵(key)不能重複;
元素類型為 [`std::pair`](#stdpair),
`first` 存放鍵(key),`second` 存放值(value)。
常見用法:
* 建構子(constructor)
* `map<K, V> mp;` / `map<K, V> mp{};`
* `K` 為鍵的類型
* `V` 為值的類型
* `mp.insert({k, v});`
* 插入一個元素,為 `k` - `v`
* `mp.erase(k);`
* 刪除鍵為 `k` 的元素
* `mp.find(k);`
* 尋找鍵等於 `k` 的元素,回傳此元素的迭代器
(若不存在則回傳 `mp.end()`)
* `mp[k];` :bulb:
* 回傳「值的參考(reference)」,
若不存在則插入一個元素,鍵為 `k`
* **建議夠熟悉再使用。**
```cpp!
map<int, string> mp{};
mp.insert({0, "I'll"});
mp.insert({2, "this"s});
mp.insert({3, "class"s});
// {0, "I'll"} {2, "this"} {3, "class"}
mp[1] = "fail"s;
// {0, "I'll"} {2, "this"} {3, "class"} {1, "fail"}
mp[1] = "passed"s;
// {0, "I'll"} {2, "this"} {3, "class"} {1, "passed"}
auto it{mp.find(1)};
if (it != mp.end()) it->second = "pass"s;
// {0, "I'll"} {2, "this"} {3, "class"} {1, "pass"}
for (auto& [k, v] : mp) cout << v << ' ';
cout << endl;
// 輸出所有元素
// < I'll pass this class
```
<hr class="thin">
#### [`std::multimap`](https://en.cppreference.com/w/cpp/container/multimap)
`std::multimap` 也與 `map` 幾乎一樣,差別只在於鍵可以重複。
<hr class="dotted">
### 無序關聯容器(Unordered associative containers):bulb:
* 搜尋的均攤複雜度 $O(1)$,最差情況可能為 $O(n)$
* 透過雜湊表(hash table)實現
> 競程中較少用到,因為一般 $O(\log n)$ 的複雜度就足夠,
> 且牽涉到雜湊函數(hash function),其餘使用與關聯容器幾乎相同,
> 此處就不進行介紹。
<hr class="dotted">
### 容器改寫器(Container adaptors)
> 提供容器一個不同的介面。
<hr class="gradient">
#### [`<stack>`](https://en.cppreference.com/w/cpp/header/stack)
<hr class="thin">
### [`std::stack`](https://en.cppreference.com/w/cpp/container/stack)
`std::stack` 是一個後進先出(LIFO, Last-In-First-Out)的資料結構。
> 預設改寫自 `deque`,因為它支援較快的尾端插入。
```graphviz
digraph {
rankdir=LR;
subgraph cluster {
style=rounded;
margin=40.0;
label="stack";
memory [style=filled, shape=record, fixedsize=true, width=1.6, height=1.6, label="<top>top()|||"];
}
node [style="filled, dashed", shape=box, width=1.6, height=0.4, label=""];
edge [style=dashed, arrowhead=o];
push :e -> memory:top :n [label="push"];
memory:top :e -> pop :w [label="pop"];
}
```
常見用法:
* 建構子(constructor)
* `stack<T> stk;` / `stack<T> stk{};`
* `stk.push(val);`
* 在頂端插入 `val`
* `stk.pop();`
* 移除頂端元素
* `stk.top();`
* 回傳頂端元素的參考
```cpp!
stack<double> stk{};
stk.push(0.);
stk.push(1.414);
stk.push(1.732);
stk.push(2.223);
// 0. 1.414 1.732 2.223
while (!stk.empty()) {
double x{stk.top()};
stk.pop();
cout << x << ' ';
}
cout << endl;
// 輸出所有元素
// < 2.223 1.732 1.414 0
```
<hr class="gradient">
#### [`<queue>`](https://en.cppreference.com/w/cpp/header/queue)
<hr class="thin">
### [`std::queue`](https://en.cppreference.com/w/cpp/container/queue)
`std::queue` 是一個先進先出(FIFO, First-In-First-Out)的資料結構。
> 預設改寫自 `deque`,因為它支援快速的頭尾操作。
```graphviz
digraph {
rankdir=LR;
subgraph cluster {
style=rounded;
margin=20.0;
label="queue";
memory [style=filled, shape=record, fixedsize=true, width=3, height=1.5, label="{<back>|||||<front>f\nr\no\nn\nt\n()}"];
}
node [style="filled, dashed", shape=box, fixedsize=true, width=0.5, height=1.5, label=""];
edge [style=dashed, arrowhead=o]
push :s -> memory:back [label="push"];
memory:front -> pop :n [label="pop"];
}
```
常見用法:
* 建構子(constructor)
* `queue<T> qu;` / `queue<T> qu{};`
* `qu.push(val);`
* 在尾端插入 `val`
* `qu.pop();`
* 移除第一個元素
* `qu.front();`
* 回傳第一個元素的參考
```cpp!
qu<double> qu{};
qu.push(0.);
qu.push(1.414);
qu.push(1.732);
qu.push(2.223);
// 0. 1.414 1.732 2.223
while (!qu.empty()) {
double x{qu.front()};
qu.pop();
cout << x << ' ';
}
cout << endl;
// 輸出所有元素
// < 0 1.414 1.732 2.223
```
<hr class="thin">
### [`std::priority_queue`](https://en.cppreference.com/w/cpp/container/priority_queue)
`std::priority_queue` 是一個二元堆積(Binary heap),
支援常數時間取得最值(absolute extrema)。
預設改寫自 `vector`,因為有較快的存取速度。
> **預設為最大堆積(max heap)**,意即最大值在頂端。
```graphviz
digraph {
node [style=rounded, shape=box, fixedsize=true, width=0.4, height=0.3];
array [shape=record, fixedsize=true, width=3, height=0.4,
label="<0>10|<1>9|<2>7|<3>8|<4>5|<5>6|<6>3|<7>1|<8>4|<9>2"];
subgraph cluster_tree {
edge [style=bold, dir=forward];
10 -> 9;
10 -> 7;
9 -> 8;
9 -> 5;
7 -> 6;
7 -> 3;
8 -> 1;
8 -> 4;
5 -> 2;
}
top [shape=none, label="top()"];
top -> array:0 [style=dotted, arrowhead=o];
edge [style=dashed, dir=none];
array:0 -> 10;
array:1 -> 9;
array:2 -> 7;
array:3 -> 8;
array:4 -> 5;
array:5 -> 6;
array:6 -> 3;
array:7 -> 1;
array:8 -> 4;
array:9 -> 2;
}
```
常見用法:
* 建構子(constructor)
* `priority_queue<T> pq;` / `priority_queue<T> pq{};`
* `priority_queue<T, vector<T>, Cmp> pq{};`
* `Cmp` 為比較函數的類別
* `pq.push(val);`
* 插入 `val`
* `pq.pop();`
* 移除最值
* `pq.top();`
* 回傳第一個元素的參考
```cpp!
priority_queue<int, vector<int>, greater<int>> pq{};
// 使用 greater 可改為最小堆積(min heap)
pq.push(6);
pq.push(2);
pq.push(9);
pq.push(4);
// 6 2 9 4
while (!pq.empty()) {
auto x{pq.top()};
pq.pop();
cout << x << ' ';
}
cout << endl;
// 輸出所有元素
// < 2 4 6 9
```
<hr class="dashed">
## [演算法(Algorithms)](https://en.cppreference.com/w/cpp/algorithm)
一般大多用於 `vector`(`array`, `deque`)這些擁有隨機存取迭代器的容器,
否則複雜度過高。
<hr class="gradient">
#### [`<algorithm>`](https://en.cppreference.com/w/cpp/header/algorithm)
<hr class="thin">
### [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort)
對區間元素進行排序。
* `sort(c.begin(), c.end());`
* 由小排到大
* `sort(c.rbegin(), c.rend());`
* 利用反向迭代器
* 由大排到小
* `sort(c.begin(), c.end(), cmp);`
* 自行給比較函數
* `cmp` 比較結果為真(`true`)的元素,會被放在較前面。
也就是如果 `cmp` 給了「大於」這樣的函數,就會由大排到小。
函數指標也符合 [C++ FunctionObject](https://en.cppreference.com/w/cpp/named_req/FunctionObject) 的定義;
為何不用寫 `&cmp` 而是 `cmp` 就足夠,可參考 [Everything you need to know about pointers in C](https://boredzo.org/pointers/#function_pointers)。
::: spoiler function
```cpp!
bool cmp(const int& a, const int& b) {
return a > b;
}
sort(v.begin(), v.end(), cmp);
```
:::
::: spoiler function object
```cpp!
struct {
bool operator()(int a, int b) {
return a > b;
}
} cmp{};
sort(v.begin(), v.end(), cmp);
```
:::
:::spoiler lambda
```cpp!
sort(v.begin(), v.end(), [](int a, int b){return a > b;});
```
:::
<br>
指向陣列的指標也符合 [C++ LegacyRandomAccessIterator](https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator)。
:::spoiler C-style array
```cpp!
int arr[100];
sort(arr, arr + 100);
```
:::
<hr class="thin">
### [`std::min`](https://en.cppreference.com/w/cpp/algorithm/min) / [`std::max`](https://en.cppreference.com/w/cpp/algorithm/max)
回傳較小(大)值。
* `mn = min(mn, x);`, `mx = max(mx, x);`
* `min(0, x);`, ...
<hr class="thin">
### [`std::reverse`](https://en.cppreference.com/w/cpp/algorithm/reverse)
反轉整個區間。
* `reverse(c.begin(), c.end());`
<hr class="thin">
### [`std::lower_bound`](https://en.cppreference.com/w/cpp/algorithm/lower_bound) / [`std::upper_bound`](https://en.cppreference.com/w/cpp/algorithm/upper_bound) / [`std::equal_range`](https://en.cppreference.com/w/cpp/algorithm/equal_range):bulb:
**區間必須為排序好的**(實際上依照 $val$ 進行分隔(partition)即可),
概念為二分搜尋(binary search)。
找尋區間中第一個不小於(大於) $val$ 的元素,回傳迭代器。
* `lower_bound(c.begin(), c.end(), val);`
* 回傳第一個不小於 `val` 的元素的迭代器
* `upper_bound(c.begin(), c.end(), val);`
* 回傳第一個大於 `val` 的元素的迭代器
* `equal_range(c.begin(), c.end(), val);`
* 回傳一個 `std::pair`,包含上述二者
* 可視為 `val` 的左閉右開區間
**如果容器為 `set`,請用 `set` 的函數成員!**
<hr class="thin">
### [`std::next_permutation`](https://en.cppreference.com/w/cpp/algorithm/next_permutation):bulb::bulb:
將區間排序變為字典序(lexicographic order)的下一個。
(沒有的話則變為字典序的第一個,並回傳 `false`)
* `next_permutation(c.begin(), c.end());`
```cpp!
vector<int> v{1, 3, 2, 4};
sort(v.begin(), v.end());
// 先回到字典序的第一個
do {
for (auto& x : v) cout << x;
cout << '\n';
} while (next_permutation(v.begin(), v.end()));
// 輸出所有排列情況
// < 1234
// < 1243
// < 1324
// < ...
// < 4321
```
> 所有排列的平均複雜度 $O(1)$;最差情況複雜度為 $O(n)$。
<hr class="thin">
### [`std::nth_element`](https://en.cppreference.com/w/cpp/algorithm/nth_element):bulb::bulb:
選擇演算法(selection algorithm),平均複雜度為 $O(n)$。
* `nth_element(c.begin(), it, c.end());`
* 將 `it` 指向的位置元素變為排序完應該在此位置的元素
(`it` 前面的不比 `it` 大;`it` 後面的不比 `it` 小)
```cpp!
vector<int> v{5, 6, 9, 0, 8, 7, 2, 0};
nth_element(v.begin(), v.begin() + 1, v.end(), greater<int>{});
// 將第二個位置排好
// 第一個元素必為最大值
for (auto& x : v) cout << x << ' ';
cout << endl;
// < 9 8 7 6 0 5 2 0
```
<hr class="dashed">
## [字串(String)](https://en.cppreference.com/w/cpp/string)
C++ 將字串的概念抽象化(Abstraction),並提供一系列的操作介面。
<hr class="gradient">
#### [`<string>`](https://en.cppreference.com/w/cpp/header/string)
<hr class="thin">
* `stoi(str);`
* 將字串 `str` 轉為整數(`int`)
* `to_string(x);`
* 將數字 `x` 轉為字串
<hr class="thin">
### [`std::string`](https://en.cppreference.com/w/cpp/string/basic_string)
`std::string` 有點類似於 `vector`,也有不少相同的函數。
(有時可用 `vector<char>` 替代)
* 常量(Literal)
* `"string"s`
* `""s` 代表是 `string` <font class="cpp14">`C++14`</font>
* 建構子(constructor)
* `string str;` / `string str{};`
* 空字串
* `string str{a};`
* 建構跟 `a` 相同的字串
* `str += a;`, `str + a;`, `str == a;`, `str > a;`, ...
* 透過運算子重載,使運算子符合字串的意義
* `os << str;`, `os >> str;`
* 可以直接輸入輸出字串
```cpp!
string s1{};
string s2{};
cin >> s1 >> s2;
// > str1
// > str2
string str{s1 + '+' + s2};
// "str1+str2"s
for (int i{0}; i < str.length(); ++i)
if (str[i] == '+') str[i] = '#';
str += "\n"s;
cout << str;
// < str1#str2
```
<hr class="thin">
### [`std::getline`](https://en.cppreference.com/w/cpp/string/basic_string/getline):bulb:
* `getline(is, str);` / `getline(is, str, delim);`
* 從輸入流 `is` 讀進 `str`,直到遇到定界符 `delim`(預設為換行 `'\n'`) 為止
* 定界符 `delim` 會被從輸入流 `is` 取出但不放入 `str` 中
**一般透過 `operator>>` 讀取過後,會有殘留的空白或換行,請特別注意。**
#### [`std::basic_istream::ignore()`](https://en.cppreference.com/w/cpp/io/basic_istream/ignore)
* `is.ignore();`
* 拋棄輸入流 `is` 的一個字元
* `is.ignore(numeric_limits<streamsize>::max(), '\n');`
* 拋棄輸入流 `is` 中的字元直到遇到換行(`'\n'`)
* 換行也會被拋棄
#### [`std::ws`](https://en.cppreference.com/w/cpp/io/manip/ws)
* `is >> ws;`
* 拋棄輸入流 `is` 前端任何的空白字元(e.g., `' '`, `'\n'`, `'\t'`, ...)
<hr class="gradient">
#### [`<sstream>`](https://en.cppreference.com/w/cpp/header/sstream)
<hr class="thin">
### [`std::stringstream`](https://en.cppreference.com/w/cpp/io/basic_stringstream):bulb::bulb:
`std::stringstream` 透過 `string` 建立一個輸入輸出流。
> **常用於讀取不定個數的輸入。**
```cpp!
int n;
cin >> n;
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// 避免 n 後面還有空白,把上一行剩餘內容全部捨棄
// 一般使用 cin.ignore() 捨棄換行即可
cin >> ws;
// 或使用 ws
// 沒有空白字元的話,相當於沒有進行操作
// 所以在這邊可以正常執行
string s{};
getline(cin, s);
// 讀取一整行
// > 1 2 4 8
stringstream ss{s};
// 創建輸入流
vector<int> v{};
while (ss >> n) v.push_back(n);
// 讀到輸入流 ss 結束
for (auto& x : v) cout << x << ',';
cout << endl;
// < 1,2,4,8,
```
<hr class="dashed">
## [工具(Utility)](https://en.cppreference.com/w/cpp/utility)
#### [`<utility>`](https://en.cppreference.com/w/cpp/header/utility)
<hr class="thin">
### [`std::swap`](https://en.cppreference.com/w/cpp/algorithm/swap)
將二個變數的值交換。
<hr class="thin">
### [`std::pair`](https://en.cppreference.com/w/cpp/utility/pair)
`std::pair` 將可以存放二個值(類型可以不相同)。
* 建構子(constructor)
* `pair<T1, T2> pr;` / `pair<T1, T2> pr{a, b};`
* `make_pair(a, b);`
* 根據 `a`, `b` 的類型創建一個 `pair` 並回傳
* `pr.first;` / `pr.second;`
* 存取第一(第二)個值
* `pr1 > pr2;`
* 先比較 `first`,相同再比較 `second`
* `first` 與 `second` 皆要為可比較的類型
<hr class="gradient">
#### [`<tuple>`](https://en.cppreference.com/w/cpp/header/tuple):bulb:
<hr class="thin">
### [`std::tuple`](https://en.cppreference.com/w/cpp/utility/tuple)
`pair` 是 `std::tuple` 的特例,`tuple` 可以存放多個值。
* 建構子(constructor)
* `tuple<T1, ...> tp;` / `pair<T1, ...> tp{a, ...};`
* `make_tuple(a, ...);`
* `get<i>(tp);`
* 存取第 `i` 個值(`i` 必須為常數)
* `tp1 > tp2;`
* 從前面的元素開始比較
* 所有元素必須為可以比較的類型
<hr class="thin">
### [Structured binding declaration](https://en.cppreference.com/w/cpp/language/structured_binding) :bulb:<font class="cpp17">`C++17`</font>
下方給一些常用的範例。
```cpp!
pair<int, char> pr{0, 'a'};
auto [fi, se]{pr};
fi = 1, se = 'b';
cout << pr.first << pr.second << endl;
// < 0a
auto& [fi_r, se_r]{pr};
fi_r = 2, se_r = 'c';
cout << pr.first << pr.second << endl;
// < 2c
```
```cpp!
vector<tuple<int, string, double>> v{};
v.emplace_back(0, "e"s, 2.71);
v.emplace_back(1, "pi"s, 3.14);
for (auto& [idx, name, value] : v)
cout << idx << ' ' << name << ' ' << value << '\n';
// < 0 e 2.71
// < 1 pi 3.14
```
<hr class="gradient">
#### [`<optional>`](https://en.cppreference.com/w/cpp/header/optional)
<hr class="thin">
### [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional):bulb: <font class="cpp17">`C++17`</font>
`std::optional` 將一個型態包裝起來,提供它存在與不存在兩種狀態,
在某些情境能夠更輕易的表達想法。
> 類似於 `pair<bool, T>`。
* 建構子(constructor)
* `optional<T> x{};` / `optional<T> x{nullopt};`
* 建構一個物件,裡面沒有值
* `optional<T> x{val};`
* 建構一個物件,裡面的值是 `val`
* `bool(x);` / `x.has_value();`
* 判斷裡面有沒有值
* 在某些地方會自動轉型
* `x.value();` / `x.value_or(val2);`
* 值的參考
* `x.value()` 只能在 `x.has_value()` 為 `true` 時呼叫
* `x.reset();`
* 初始(變為沒有值的狀態)
* `x = y;`
* 賦值,`y` 的類型可以是 `T` 或 `optional<T>`
<hr class="dotted">
### [`std::move()`](https://en.cppreference.com/w/cpp/utility/move):bulb::bulb:
C++ 的 [Value categories](https://en.cppreference.com/w/cpp/language/value_category) 分為五類。
Primary categories :
* lvalue
* has identity and cannot be moved from
* xvalue
* an “eXpiring” value
* has identity and can be moved from
* pvalue
* “pure” rvalue
* has identity and can be moved from
Mixed categories :
* glvalue
* “generalized” lvalue
* either lvalue or xvalue
* rvalue
* either prvalue or xvalue
```graphviz
digraph {
rankdir=UD
lvalue [shape=plaintext];
glvalue [shape=plaintext];
xvalue [shape=plaintext];
rvalue [shape=plaintext];
prvalue [shape=plaintext];
lvalue -> glvalue :nw [style=dashed, arrowhead=curve];
xvalue -> glvalue :ne [style=dashed, arrowhead=curve];
xvalue -> rvalue :nw [style=dashed, arrowhead=curve];
prvalue -> rvalue :ne [style=dashed, arrowhead=curve];
}
```
> std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object.[^cppmove]
簡單來說,`std::move()` 就是將物件轉為一個 xvalue expression,
代表說這個物件的已經過期(expiring)了,資源可以重複利用。
常用於容器,使用 move constructor 或 move assignment。
(透過 rvalue reference (`T&&`) 與 lvalue reference (`T&`) 型態上的差別,
選擇不同的重載(overloading)函數,關於 reference 可參考 [Reference declaration](https://en.cppreference.com/w/cpp/language/reference)。)
像是 `vector`,可以將內部配置的陣列直接轉移給另外一個 `vector`。
> 雖然沒有明確規範,但大多實作下,`v` 都會變成空的 `vector`。
> > Unless otherwise specified, all standard library objects that have been moved from are placed in a "valid but unspecified state".[^cppmove]
```cpp!
vector<int> v(10, 0);
vector<int> v2{move(v)}; // move constructor
assert(v.empty());
assert(v2.size() == 10);
vector<int> v3(5, -1);
v3 = move(v2); // move assignment
assert(v2.empty());
assert(v3.size() == 10);
```
> :bulb::bulb::bulb: 如果有人好奇為什麼 `std::move()` 的參數只有 `T&&`,
> 可以參考 [Template argument deduction](https://en.cppreference.com/w/cpp/language/template_argument_deduction)。
> > If P is an rvalue reference to a cv-unqualified template parameter (so-called forwarding reference), and the corresponding function call argument is an lvalue, the type lvalue reference to A is used in place of A for deduction.[^cppTAD]
> >
> > 註:P is the parameter's type; A is the argument's type.
<hr class="dashed">
## [數值(Numerics)](https://en.cppreference.com/w/cpp/numeric)
#### [`<numeric>`](https://en.cppreference.com/w/cpp/header/numeric)
<hr class="thin">
### [`std::gcd`](https://en.cppreference.com/w/cpp/numeric/gcd) / [`std::lcm`](https://en.cppreference.com/w/cpp/numeric/lcm) <font class="cpp17">`C++17`</font>
計算最大公因數(greatest common divisor),最小公倍數(least common multiple)。
<hr class="gradient">
#### [`<cmath>`](https://en.cppreference.com/w/cpp/header/cmath)
<hr class="thin">
### [`std::abs`](https://en.cppreference.com/w/cpp/numeric/math/abs)
取絕對值(absolute value)。
<hr class="thin">
### [`std::sqrt`](https://en.cppreference.com/w/cpp/numeric/math/sqrt)
計算開根號,**參數與回傳值皆為浮點數**,請注意精度問題。
> std::sqrt is required by the IEEE standard to be exact. After rounding to the return type (using default rounding mode), the result of std::sqrt is indistinguishable from the infinitely precise result.[^cppsqrt]
<hr class="dashed">
## 數值限制(Numeric limits)
#### [`<limits>`](https://en.cppreference.com/w/cpp/header/limits)
<hr class="thin">
### [`std::numeric_limits`](https://en.cppreference.com/w/cpp/types/numeric_limits):bulb:
`std::numeric_limits` 定義一些算術類型(arithmetic types)的性質。
```cpp!
cout << numeric_limits<int>::max() << '\n';
// < 2147483647
```
---
標準函數庫中還有許多函數,雖然有些很簡單,可以自己寫,
不過**標準函數就如同一種語言**,能夠幫助我們更輕易地表達,
希望讀者再日後的使用中,能夠逐漸熟悉標準函數庫的使用。
---
# References
* Stroustrup, Bjarne (2012). "The C++ Programming Language (Fourth Edition)"(https://en.cppreference.com/w/)
* Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, & Clifford Stein (2009). "Introduction to Algorithms (Third Edition)".
* [C++ reference]
---
# 練習題
| Problem | Tags |
|:-:|:- |
| [字典](https://zerojudge.tw/ShowProblem?problemid=a541) | `set` |
| [文字抄寫 II](https://zerojudge.tw/ShowProblem?problemid=d518) | `map` |
| [今晚打老虎](https://zerojudge.tw/ShowProblem?problemid=a091) | `map` or `multiset` |
| [p&q的邂逅](https://zerojudge.tw/ShowProblem?problemid=a565) | `stack` |
| [Waiting In Line](https://zerojudge.tw/ShowProblem?problemid=d718) | `queue` |
| [手續費 (Fee)](https://tioj.ck.tp.edu.tw/problems/1424) | `priority_queue` |
| [成績指標](https://zerojudge.tw/ShowProblem?problemid=b964) | `sort` |
| [Sort! Sort!! and Sort!!!](https://zerojudge.tw/ShowProblem?problemid=d750) | `sort` |
| [二分搜尋法](https://zerojudge.tw/ShowProblem?problemid=d732) | `lower_bound` |
| [The Hamming Distance Problem](https://zerojudge.tw/ShowProblem?problemid=d790) | `next_permutation` |
| [提款卡密碼](https://zerojudge.tw/ShowProblem?problemid=a065) | `string` |
| [一串加](https://zerojudge.tw/ShowProblem?problemid=b581) | `stringstream` |
[^TC++PL]: Stroustrup, Bjarne (2012). "The C++ Programming Language (Fourth Edition)", p.1268.
[^CLRS]: Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, & Clifford Stein (2009). "Introduction to Algorithms (Third Edition)", p.451.
[^INTERVAL]: [Why numbering should start at zero](https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html)
[^clear]: [What is the complexity of std::vector<T>::clear() when T is a primitive type?](https://stackoverflow.com/questions/11235975/what-is-the-complexity-of-stdvectortclear-when-t-is-a-primitive-type)
[^DQ]: [https://stackoverflow.com/questions/6292332/what-really-is-a-deque-in-stl](https://stackoverflow.com/questions/6292332/what-really-is-a-deque-in-stl)
[^cppCtr]: [C++ named requirements: Container](https://en.cppreference.com/w/cpp/named_req/Container)
[^cppFO]: [Function objects](https://en.cppreference.com/w/cpp/utility/functional)
[^cppsqrt]: [std::sqrt](https://en.cppreference.com/w/cpp/numeric/math/sqrt)
[^cppmove]: [std::move](https://en.cppreference.com/w/cpp/utility/move)
[^cppTAD]: [Template argument deduction](https://en.cppreference.com/w/cpp/language/template_argument_deduction)
<style>
.cpp14 {
font-size: 0.7em;
color: #228B22;
}
.cpp17 {
font-size: 0.7em;
color: #B22222;
}
hr.thin {
height: 0.5px;
}
hr.dotted {
border-top: dotted;
height: .0em;
color: #777777;
background-color: #ffffff;
}
hr.dashed {
border-top: dashed;
height: .0em;
color: #777777;
background-color: #ffffff;
}
/* Gradient transparent - color - transparent */
hr.gradient {
border: 0;
height: 1px;
background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
}
/* Flaired edges, by Tomas Theunissen */
hr.flaired {
overflow: visible;
height: 30px;
border-style: solid;
border-color: black;
border-width: 1px 0 0 0;
border-radius: 20px;
background-color: #ffffff;
}
hr.flaired:before { /* Not really supposed to work, but does */
display: block;
content: "";
height: 30px;
margin-top: -31px;
border-style: solid;
border-color: black;
border-width: 0 0 1px 0;
border-radius: 20px;
}
/* h1, h2, h3, h4, h5, h6, span {
background-image: linear-gradient(to right, red, orange, yellow, green, blue, indigo, purple);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
} */
</style>