owned this note
owned this note
Published
Linked with GitHub
# containers
> STL containers == Standard Template Library containers
---
## 前言
這是我怕有些初階班剛上進階班對 `containers` 還不熟,所以才寫了這 $35000$ 多字的講義 (經過一些增修後超過了 `49000` 字)
雖然和學長的講義很像,而且可能也沒有比較詳細,但還是加減看一下 :poop:
---
## NOT STL containers
### 傳統一維陣列
實用程度: :star: :star: :star: :star: :star:
初階班前幾堂就會碰到了,這邊小講一下
使用時不用任何標頭檔,他是你為這一個變量分配一串記憶體
:::success
:::spoiler 沒學過指標、參照、迭代器但想學的點我
可以看 [$pointer$ 初階班講義 $by$ 第 $11$ 屆初階教學 **samsonjaw**](https://hackmd.io/@samson-note/pointer_reference)
#### 指標 (pointer, *)
指標是可以存取一個記憶體位置的變數,但說穿了他和 `int` 之類的一樣是型別
他的宣告方式,在變數名字前加一個 `*`
要取值的話也是在指標變數前加一個 `*`
若沒有初始化或被指派,去存取他的值會出問題 (大概率 `RE`)
因此我們常在宣告時就直接指派一個空指標 `nullptr` 給他
指標更多的用法是用 `new` 來分配新的記憶體給他,讓他不用依賴其他變數的記憶體位置
##### 宣告指標
```cpp
int *a, *b; // a and b are pointers
int* a, b; // b isn't a pointer
```
##### * 在 C++ 中的六種出現情形:
1. 乘法 `(a * b)`
2. 乘法賦值 `(a *= b)`
3. 指標宣告 `(int *a, *b)`
4. `call by address` `( void modify(int *arr, const int len) )`
5. 取值 `get value` (指標,`pointer` ) `(cout << *ptr << endl)`
6. 取值 `get value` (迭代器,`iterator` ) `(cout << *vec.begin() << endl)`
##### 指標使用範例
```cpp=
#include <iostream>
using namespace std;
void addition(int *x, int *y) { // call by address
*x = 1, *y = 1; // 取址,將 x, y 兩者記憶體儲存的值改成 1
cout << *x + *y << endl; // 輸出 2
}
int main() {
int b = 10, c = 100;
int *ptr = &b, *a = &c; // 指標宣告
cout << *ptr << endl; // 輸出 10
*ptr = 30; // 直接將 b 的記憶體儲存的值改成 30
cout << b << endl; // 輸出 30
addition(ptr, a);
cout << *ptr << ' ' << *a << endl; // 輸出 1 1
cout << ptr << endl; // 會輸出一個記憶體位址名稱,如: 0x6ffdfc
return 0;
}
```
#### 參照 (reference, &)
有的人又叫他參考、引用,上次 **APCS** 有考出這個菜鳥殺手
和 `pointer` 一樣,若要宣告一個參照型別 `(reference type)`,在變數名字前加一個 `&`
而且在宣告時就要初始化他所參照的對象
可以將參照想像成是將要替代的東西取一個別名,兩者指向同一個記憶體位址,而且都可以對其進行存取、修改
而 `call by reference` 只有 **C++** 才有,**C** 語言只有 `call by pointer`
`call by reference` 相較之下方便、好寫很多
參照是直接拿取記憶體位置,因此如果在大量需要呼叫 ($e.g.$ 遞迴、快速冪) 等程式碼中,若沒有加參考可能會將時間浪費在拷貝數據上,進而導致 `TLE`
順帶一提,最基本的 `call by value` 就是將你給的變數複製一份到新的記憶體位置上再進行運作
##### 宣告指標
```cpp
int &a = b, &c = d; // a and c are references
int& a = b, c = d; // c is just a normal int value type
```
##### & 在 C++ 中的六種出現情形:
1. 位元運算 `bitand` `(a & b)`
2. 位元 `and` 賦值 `and_eq` `(a &= b)`
3. 邏輯運算 `and` `(a && b)`
4. 參照宣告 `(int &a, &b)`
5. `call by reference` `( void modify(int &x, int &y) )`
6. 取址 `get address` `(*ptr = &val)`
##### 參照使用範例
```cpp=
#include <iostream>
using namespace std;
void sub(int &x, int &y) { // call by reference,對這邊改動也會影響主程式中的變數
x = y;
y = -8;
cout << x - y << endl; // 輸出 10
}
int main() {
int a = 1, b = 2;
int &ref = a; // 宣告參照 ref,此時將 ref 看作是 a 的別名
int *ptr = &b; // 進行取址,將 b 的記憶體位址指派給 *ptr 這個指標
cout << (a & b) << endl; // 輸出兩者的位元運算值:0
if (a && b) // 若 a、b 皆為 true
cout << *ptr * ref << endl; // 輸出指標 ptr 取值乘上參照 ref 的值:2
a &= b; // a = a & b,現在 a 為 0,同樣地,ref 也是 0
sub(ref, b);
cout << a << ' ' << *ptr << endl; // 輸出 2 -8
return 0;
}
```
##### 迭代器 (iterator)
他跟指標 `pointer` 很像,簡單來說容器專用的 `pointer`,在前面有提到,也是一樣用 `*` 來進行取值
許多 `STL` 都有支援 `iterator`
不過注意 `stack`、`queue` 沒有支援!
如果用了會吃 `CE`
範例搭配下面的成員函式存取來講
`begin()` 指的是最開始那格
`end()` 指的是最後面的後面那格
**C++** 的 `iterator` 以左閉右開為主,請大家不要弄錯
![](https://i.imgur.com/L3epXfD.png)
##### 成員函式存取 (. 、 ->)
用 `.` 來存取一個物件的成員函式 ( `struct` / `class` )
當你要存取一個指標內的成員函式的話,應該用 `->` 較為方便,而不是 `*` 搭配 `.`
因為 **C++** 有一個名為運算子優先順序的東西
例如 `* /` 在 `+ -` 前面,而 `.` 較 `*` 優先,因此若要用需要加 `()` 提升他的優先程度,就像數學式一樣
- `begin()` 最常用,指向 `[0]` 這格
- `end()` 最常用,指向 `back()` 後面那格
- `rbegin()` 次常用,指向 `back()`
- `rend()` 次常用,指向 `[0]` 前面那格
- `cbegin()` 少見,`const begin()` 就是只能存取不能修改,但用 `begin()` 就好
- `cend()` 少見,`const end()` 就是只能存取不能修改,但用 `end()` 就好
- `crbegin()` 極少見,`const rbegin()` 就是只能存取不能修改,但用 `rbegin()` 就好
- `crend()` 極少見,`const rend()` 就是只能存取不能修改,但用 `rend()` 就好
##### 使用範例
```cpp=
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int len = 100;
vector < pair<int, int> > vec(100);
for (int i = 0; i < len; i++) {
vec[i].first = i;
vec[i].second = i * len;
}
vector<int>::iterator iter;
for (iter = vec.begin(); iter != vec.end(); iter++) {// ++ 有被 overload
// it = next(it);
cout << iter -> first << ' ' << iter -> second << endl;
// iter -> first 寫成 (*iter).first 也可以
}
return 0;
}
```
##### APCS考題範例
以下程式碼輸出為何? (**APCS** 考題以 **C** 語言命題)
```c=
#include <stdio.h>
void add(int x, int y) {
x = 1;
y = 1;
printf("%d ", x + y);
}
int main() {
int a = 3, b = 5;
add(a, b);
printf("%d\n", a + b);
return 0;
}
```
:::spoiler 答案點我:
```
2 8
```
答對了嗎?因為沒有加 `reference`,這一題是 `call by value`,在上面函數內改動的值不會影響到 `main` 中變數的值
但是很多人會答出 $2\ 2$
不過通常 **APCS** 都是考 **C** 語言,而非 **C++**,因此不會有 `call by reference` 出現
:::
#### 宣告:
```cpp
int arr[n] = {};
// 基本款,n 是陣列大小
// = {} 可不打,若不打不初始化
// 大括號中沒東西是初始化為 0
// 若大括號中元素數量不足 n 個,那會將元素優先初始化給前面項,沒有初始化到的自動初始化為 0
// 因此可以直接打 {0} 來得到全部初始化為 0 的陣列
int arr[] = {a1, a2, a3, ..., an};
// 可以不指定大小,但後面一定要有初始化
// 大括號中的元素數量即為陣列大小,非不定大小
```
:::warning
:::spoiler 陣列傳遞
若要傳遞陣列到函式中,要注意用法:
```cpp
void (int *arr,int len)
void (int arr[], int len)
// 若要傳遞的話不能打出陣列大小,而這裡的 arr[] 相當於 arr*,是傳遞一個位址 address
// 因此需要再傳遞一個變數為 arr 的大小以免溢位
```
試著將一串 $0$ 的陣列變成一串 $1$ 的陣列 (使用 `call by pointer`)
範例 `code`:
```cpp=
#include <iostream>
using namspace std;
void modify(int *a, const int len) {
for (int i = 0; i < len; i++)
a[i] = true;
}
int main() {
const int n = 3;
int arr[n] = {};
for (int i = 0; i < n; i++)
cout << arr[i] << ' ';
cout << endl;
modify(arr, n);
for (int i = 0; i < n; i++)
cout << arr[i] << ' ';
cout << endl;
return 0;
}
```
:::
#### 使用範例
將一串數字進行 `bubble sort`
```cpp=
#include <iostream>
#include <algorithm> // for swap
using namespace std;
int main() {
int n;
cin >> n;
int arr[n];
for (int i = 0; i < n; i++)
cin >> arr[i];
for (int i = 0; i < n - 1; i++)
for (int j = i + 1; j < n; j++)
if (arr[i] > arr[j])
swap(arr[i], arr[j]);
for (int i = 0; i < n; i++)
cout << arr[i] << ' ';
cout << endl;
return 0;
}
```
:::spoiler **毒區退散** 若對指標很熟,可以做動態記憶體配置,這樣常數小又抵得上半個 `vector`:
#### 範例程式碼
```cpp=
#include <iostream>
#include <cstdlib> // malloc
#include <cstring> // memset
//#include <stdlib.h>
//#include <string.h>
int main() {
int *arr;
int len = 10;
arr = new int[len];
// C: arr = (int*) malloc(len * sizeof(int)); 我用這個
memset(arr, 0, len * sizeof(int));
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
// 也可以將 arr[i] 寫成 *(arr + i)
delete [] arr;
// C: free(arr); 我用這個
return 0;
}
```
#### malloc
先宣告一個整數指標、所需長度
用 `malloc` 分配記憶體給他
他是 **C** 語言時期的產物,因此他也位於 `<stdlib.h>` 標頭檔中
但許多在 **C** 語言時期就存在的函式在 **C++** 中被寫入 `iostream`
也就是說,這些函式只要 `#include <iostream>` 即可使用
不用再另外去找原本定義他的標頭檔
這裡的 `calloc`、`malloc`、`realloc`、`free` 四個函式就是例子
但小心 `memset` 沒有被定義
在標頭檔 `<cstdlib>` 中 `malloc` 函數定義如下:
```cpp
void *malloc( size_t size )
```
當我們用完之後,他回傳的是一個 `void*` 的記憶體位置,我們應該要將他指定為 `int*` 以避免後面操作時出問題
而要小心的部份是,若沒有成功分配記憶體位置 (過大),則函式會 `return` 一個空指標
`nullptr`,而在 **C** 語言中是 `NULL`
因此分配完後在不確定的情形下,應該進行檢查:
```cpp=
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
int main() {
int *arr;
int len = 10;
arr = (int*) malloc(len * sizeof(int));
if (arr == nullptr) {
cout << "failed to allocate the memory\n";
return 1; // 代表程式出錯
}
memset(arr, 0, len * sizeof(int));
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
free(arr);
return 0;
}
```
#### memset
`memset` 在標頭檔 `<cstring>` 中定義如下:
同樣是 **C** 語言時期的產物,因此他也位於 `<string.h>` 標頭檔中
```cpp
void *memset( void* dest, int ch, std::size_t count )
```
第一格放要指派的記憶體位置
第二格放指定的數字
最後放大小,而大小是用「字節」(位元組) 為單位,也就是以 `byte` 為單位
要小心 arr 是一個指標,指標大小為 `8 bytes`,所以在這形況下不能用 `sizeof(arr)`
因此我們用 `len` 去乘上一個位置,也就是 `int` 的 `size` 這樣就可以成功計算所需的記憶體數量
不過 `sizeof(arr*)` 代表著那一個指標所代表的值記憶體大小,因此可以寫成 `sizeof(arr*) * len`
註:資料型態占的位元組 `(bytes)`
- `bool = 1`
- `char = 1`
- `int = 4`
- `float = 4`
- `long long = 8`
- `double = 8`
#### calloc
同時,這兩個也可以用一個 `calloc` 函式合併
`calloc` 和 `malloc` 不同之處在於 `calloc` 在分配記憶體位置時會同時將他初始化為 $0$
參數的傳入也有所不同,他要求兩個參數,前者為數量,後者則是每格記憶體位置大小
同時他也在 `<cstdlib>` 函式庫中定義
定義如下:
```cpp
void *calloc( size_t count, size_t size )
```
他若沒有成功分配記憶體位置也會回傳空指標 `nullptr`、`NULL`
以上面的程式碼為例:
```cpp=
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
int main() {
int *arr;
int len = 10;
arr = (int*) malloc(len * sizeof(int));
if (arr == nullptr) {
cout << "failed to allocate the memory\n";
return 1; // 代表程式出錯
}
memset(arr, 0, len * sizeof(int));
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
free(arr);
return 0;
}
```
相當於是
```cpp=
#include <iostream>
#include <cstdlib>
using namespace std;
int main() {
int *arr;
int len = 10;
arr = (int*) calloc(len, sizeof(int));
if (arr == nullptr) {
cout << "failed to allocate the memory\n";
return 1; // 代表程式出錯
}
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
free(arr);
return 0;
}
```
#### realloc
`realloc` 是針對已分配的記憶體來重行分配
與下面會提到的 `resize` 相似
和 `malloc` 不同之處在於他會保留原本已被分配的值
他的處理方式有分為 $3$ 種情形
不過因為和我們的應用較沒關係,因此這邊不多做介紹,有興趣可以自己去上網學習
他需要兩個參數,前者是分配的基礎,後者則是分配的記憶體總量
```cpp
void *realloc( void* dest, std::size_t count )
```
和上面的兩個 `alloc` 一樣,都會在記憶體分配失敗時回傳一個空指標 `nullptr` 或 `NULL`
程式碼舉例:
```cpp=
#include <iostream>
#include <cstdlib>
using namespace std;
int main() {
int len = 10;
int *arr = (int*) calloc(len, sizeof(int));
if (arr == nullptr) {
cout << "failed to allocate the memory\n";
return 1; // 代表程式出錯
}
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
len = 20;
arr = (int*) realloc(arr, len * sizeof(int));
for (int i = 0; i < len; i++)
cout << arr[i] << ' '; // 後面十位不會被初始化為 0
cout << endl;
free(arr);
return 0;
}
```
#### 定義於 iostream 中的函式
:::spoiler * 代表我看過的,後面部份我認識的有解釋
數學類函式:
1. `abs(int x)` * 絕對值,但要注意這是 **C** 語言中 `<stdlib.h>` 所定義的 `abs` , 而不是 `<math.h>` 中定義的,他沒有 `overload` 浮點數
2. `fabs(double x)` 對浮點數取絕對值,有了 `<math.h>` 就不重要了
3. `pow(double x, double a)` * $x^a$
4. `sqrt(double x)` * 開平方根
5. `exp(double x)` * $e^x$
6. `log(double x)` * 小心這裡的 $log$ 是自然對數 $ln$,也就是 $log_e$
7. `log10(double x)` * $log_{10}$ 也就是常用對數 $log$
8. `ceil(double x)` * 取上界,正數進位,負數捨去小數
9. `floor(double x)` * 取下界,正數捨去小數,負數小數變 $-1$
10. `trunc(double x)` * 將小數捨去
11. `round(double x)` * 四捨五入到「整數位」
12. `lround(double x)` 將四捨五入完的數轉成 $long$
13. `llround(double x)` 將四捨五入完的數轉成 $long long$
14. `fmod(double x, double denum)` 小數取模,`return` 值為正
15. `remainder(double x, double denum)` 也是小數取模,`return` 值有可能為負
16. `max(double a, double b)` * 取大值
17. `min(double a, double b`) * 取小值
18. `fmax(double a, double b)` 小數用的,`max` 已經 `overload` 過了,沒用
19. `fmin(double a, double b)` 小數用的,`min` 已經 `overload` 過了,沒用
20. `nextafter`
21. `nexttoward`
字元分類和操作函式:
1. `isalpha(char c)` * 是字母
2. `isalnum(char c)` * 是英數字母
3. `isblank(char c)` * 是 ' '、'\t'
4. `iscntrl(char c)` 是控制字元
5. `isdigit(char c)` * 是數字
6. `isgraph(char c)` 不屬於 `iscntrl && isblank`
7. `islower(char c)` * 是小寫字母
8. `isprint(char c)` * 是可印字元,不屬於 `iscntrl`
9. `ispunct(char c)` 是符號
10. `isspace(char c)` * 是 ' '、'\t'、'\n'、'\v'、'\f'
11. `isupper(char c)` * 是大寫字母
12. `isxdigit(char c)` 屬於十六進位中的表示字元
13. `tolower(char c)` * 若為大寫字母轉小寫
14. `toupper(char c)` * 若為小寫字母轉大寫
簡單而言:
```cpp
islower || isupper => isalpha
isalpha || isdigit => isalnum
isalnum || ispunct => isgraph
isgraph || isblank => isprint
```
字串操作函式:簡單介紹,好複雜
1. `strcpy(char *dest, char *src)` * 將 $src$ 複製到 $dest$
2. `strncpy(char *dest, char *sec, size_t n)` * 和上面一樣但有指定大小
3. `strcat`
4. `strncat`
5. `strcmp(char* a, char* b)` * 比較兩個字元陣列,相同為 $0$,不同若前者字典序較後,`return 1`;後者字典序較後,`reutrn -1`
6. `strncmpstrncpy(char *a, char *b, size_t n)` * 和上面一樣但有指定大小
7. `strcoll`
8. `strxfrm`
9. `strlen(char *str)` * 回傳字元陣列大小,且不含 '\0' 字元
10. `strerror`
11. `strchr(char* str, char c)` * 找到最先出現的字元位置,回傳一個字元指標 `char pointer`,找不到回傳空指標 `nullptr`
12. `strrchr(char* str, char c)` * 找到最後出現的字元位置,回傳一個字元指標 `char pointer`,找不到回傳空指標 `nullptr`
13. `strstr(char* str, char *s)` * 找到最先出現的子字串位置,回傳一個指向找到的第一個字母其字元指標 `char pointer`,找不到回傳空指標 `nullptr`
14. `strtok`
15. `strspn`
16. `strcspn`
動態記憶體分配函式:上面都有介紹
1. `malloc` *
2. `calloc` *
3. `realloc` *
4. `free` *
輸出入函式:不介紹,有興趣可以自己查
1. `fopen` *
2. `freopen` *
3. `fclose` *
4. `fread` *
5. `fwrite` *
6. `fgetc` *
7. `fgets` *
8. `fputc` *
9. `fputs` *
10. `getchar` *
11. `putchar` *
12. `puts` *
13. `getc` *
14. `putc` *
15. `ungetc`
16. `ftell`
17. `fseek`
18. `rewind`
19. `clearerr`
20. `feof`
21. `ferror`
22. `perror`
`time_t` 這個型別 `type` 也有,但是他的函式都全都在 `<ctime> / <time.h>` 中定義,只有這個沒什麼用
:::
---
### 傳統二維陣列
他和傳統一維陣列一樣不用 `include` ,初階班也一定教過和用過,這邊也是小提一下就好
#### 宣告:
```cpp
int arr[row][column] = {};
int arr[2][2] = {{1, 2}, {3, 4}};
// 若要初始化不為 0,要用兩層大括號,中間用逗號隔開
int arr[][2] = {{1,2},{3,4},{5,6}};
// 一樣可以第一個中括號不指定大小讓編譯器自行判斷
```
而若用動態二維陣列就是:
```cpp
int **arr; //用法比較複雜,我個人不推薦使用,除非常數真的壓很緊之類的
```
:::info
:::spoiler **11th 進階助教有話說**
若想學二維動態陣列的請自學 (但對你們知道其實**有了 `vector` 就不會有人用動態記憶體配置了**,而且我也沒有用過),喔然後**自學也是學程式很重要的一部份**,像這位 `Frankie` 學長也是從高一自學到現在才那麼電的,反正最後我想說,
:::spoiler
**幫我開直播** 拿著 照我 師傅確定要拍嗎? **我中了兩槍....** 希望大家 如果我這一次不幸死的話 請大家一定要傳承我的精神一定要傳承我的精神 **啊啊啊啊啊啊啊啊** 請大家照顧我的老婆跟小孩拜託大家 拜託大家還有我的媽媽 拜託大家 **啊啊啊啊啊啊啊啊**
![](https://i.imgur.com/i9UPM0R.gif)
就這樣,祝各位有個美好的一天
如果 `Frankie` 講到這邊,我應該不是躺在床上耍廢就是去溫泉
:::
---
### 傳統多維陣列
就是傳統二維陣列的延伸
#### 宣告:
```cpp
int arr[height][width][length]; // 三維
int arr[A1][A2]...[AN] // N維
```
:::warning
:::spoiler 有沒有多重指標呢?
有的!
```cpp=
int val = 2;
int *arr1 = &val;
int **arr2 = &arr1;
int ***arr3 = &arr2;
int ***cubicarray; // 三維陣列
```
:::
---
### string
實用程度::star: :star: :star: :star: :star:
大家在初階班應該都已經學過很多,他只要 `#include <iostream>` 就可以使用
但有部份功能需要 `#include <string>` 才能使用 (應該吧?)
其實他和 `vector` 幾乎一模一樣,只是多了以 `operator +` 來串接字符、字串
可以把他當作 `vector <char>` 的加強版
#### 宣告:
```cpp
string s(N, V);
// N 是大小,不加預設為空
// V 是每一格的值,有大小後一定要加
string s(it1, it2);
// it1、it2 是其它以 string 為的迭代器,左閉右開複製到 s 中
string s(str);
string s{str};
string s = str;
// 將整個 str 複製到 s 中
string s = "";
string s = ""s;
string s("");
string s{""};
string s{"", len}; // len 是前面字符串的長度,可不加
// 雙括號中可以直接放想要的字串
注意:若雙括號中有嵌入 '/0' 字符 (空字符),那會讀到這個字符為止
string s = "ab\0\0cd"; // s = "ab"
string s{"ab\0\0cd", 6}; // s = "ab\0\0cd"
string s = "ab\0\0cd"s; // s = "ab\0\0cd"
// 可以用 {str, len} 或者 operator ""s 來避免這個問題
// operator ""s 回傳值即為 {str, len}
```
#### 複雜度
- 隨機存取快 $O(1)$
- 從尾端增刪元素快 $O(1)$
- 串接兩字串 $O(N)$
- 中間增刪元素極慢 $O(N)$
- find $O(N)$
- clear $O(N)$
- size $O(1)$
- empty $O(1)$
#### 成員函式 and 操作
:::spoiler * 是常用等級
- ***** `operator[]`
- ***** 雖然沒有 `push_front()` 但可以用 `s = "..." + s` 來進行 (`+` 已被 `overload`)
- ***** `pop_back()`
- **** `begin()`
- **** `end()`
- **** `length()`
- *** `back()` 等同 `[s.size() - 1]`
- *** `size()` 等同 `length()`
- *** `clear()` 等同 `s = ""`
- *** `empty()` 等同 `s.size() == 0`
- *** `find`()
- *** `resize()`
- *** `fill()`
- *** `insert()`
- *** `erase()`
- *** `replace()`
- *** `push_back()` 被 `+=` `overload` 了
- *** `append()` 也是被 `+=` `overload`
- ** `front()` 等同 `[0]`
- ** `string::npos`
- ** `rfind()`
- ** `rbegin()`
- ** `rend()`
- ** `substr()`
- ** `compare()` 等同 `!=` ,但回傳值是一個 `sign` 函數,可判斷大小 (- 是小,+ 是大,0 是相等)
- \* `swap()`
- \* `cbegin()`
- \* `cend()`
- \* `at()` 等同 `[]`
- \* `copy()`
- \* `find_first_of()`
- \* `find_last_of()`
- `find_frist_not_of()`
- `find_last_not_of()`
- `crbegin()`
- `crend()`
- `data()`
- `c_str`
- `shrink_to_fit()`
- `emplace()` 很像 `insert`
:::
#### 使用範例
比對兩個字串相同與否
```cpp=
#include <iostream>
using namespace std;
int main() {
string s1, s2;
cin >> s1 >> s2;
if (s1 == s2)
cout << "The Same\n";
else
cout << "Not The Same\n";
return 0;
}
```
---
### bitset
==**11th** 進階教學覺得很香,可以看看 :poop:==
實用程度: :star:
在標頭檔 `<bitset>` 中定義,他內部已經有 `#include <string>`
他對空間優化極大,相對於 `bool array` 每一格是 `1 byte` 他每一格只占 `1 bit`,是普通陣列的 $\frac{1}{8}$
不過在一些題目中,他對數字的二進位運算非常強大,有優化常數大約 $32$ 倍左右,不無小補
`bitset` 最右方是第 $0$ 格,和平時的陣列相反,請大家注意
#### 宣告:
```cpp
bitset <N> b(s, pos, n);
// N 需要是 constexpr,就是要在編譯期就知道的常數
// 不加括號預設為0
// s 是由 '0'、'1' 所組成的字串
// 後面的括號是從 s 的 pos 到 n
bitset <N> b(u);
bitset <N> b(ull);
// u / ull 是無號整數,N 開不夠的話只保留最後 N 位
```
#### 成員函式 and 操作
:::spoiler * 是常用等級
##### 成員函式
- ***** `operator[]`
- **** `count()` `return` `true` 的數量
- **** `to_string()`
- **** `to_ulong()`
- **** `to_ullong()` **C++11**
- *** `size()`
- *** `flip()` 將每一位都取反
- *** `set()` 將整個設成 $1$
- *** `reset()` 將整個設成 $0$
- ** `all()` 是不是都是 $1$ **C++11**
- ** `any()` 有沒有 $1$
- ** `set(pos, val)` $val$ 不打預設 `true` 將 $pos$ 位設值,等同 `b[pos] = val`
- ** `reset(pos)` 將 $pos$ 位設成 $0$,等同 `set(pos, false)` 和 `b[pos] = 0`
- ** `flip(pos)` 將 $pos$ 位取反,等同 `b[pos] = !b[pos]`
- \* `none()` 是不是沒有 $1$,等同 `!any()`
- \* `test(pos)` 等同 `b[pos]`
##### 操作 (除了輸出、入,都有大量優化過)
- & bitand `(b2 = b & b1)`
- &= and_eq `(b &= b1)`
- | bitor `(b2 = b | b1)`
- |= or_eq `(b |= b1)`
- ^ xor `(b2 = b ^ b1)`
- ^= xor_eq `(b ^= b1)`
- ~ compl `(b = ~b1)`
- \<< leftshift `(b = b1 << 1)`
- \>> rightshift `(b = b1 >> 1)`
- \<<= leftshift_eq `(b <<= 1)`
- \>>= rightshift_eq `(b >>= 1)`
- \>> istream `(cin >> b)`
- \<< ostream `(cout << b)`
:::
#### 使用範例
經典問題:集合之聯集、差集、交集
```cpp=
#include <iostream>
#include <bitset>
using namespace std;
int main() {
bitset <1000> b1, b2;
cin >> b1 >> b2;
cout << (b1 & b2).count() << endl; // 交集
cout << (b1 | b2).count() << endl; // 聯集
cout << (b1 & ~b2).count() << endl; // 差集
return 0;
}
```
---
## STL containers
他全名是 `Standard Template Library containers`,是利用 `template` 的實作,來使每一個容器可以自定義要放入的元素為何,而重點就在於 `< >` 這兩片角括號能不能放多樣型別
### list
實用程度: :star:
在標頭檔 `<list>` 中定義
這難用又跟 `deque` 很像,不過是用分隔的記憶區塊串接的,不支援隨機存取
他所有的出現幾乎都要和 `hash table` 搭配
也就是說,他都要與 `unordered_map` 搭配使用,非常複雜
若真的要用,建議自己使用 `vector <struct>` 來代替他的性質
他用 `double-linked-list` 實作,所以移動刪除都是 `O(1)`
#### 宣告:
```cpp
list <T> l(N, V);
// T 是資料型態
// N 是大小,不加預設為空
// V 是每一格的值,要有大小後才能加,不加預設為空或 0
list <T> l(it1, it2);
// it1、it2 是其它以 list 為基底的容器迭代器,左閉右開複製到 list 中
list <T> l(l2);
list <T> l{l2};
list <T> l = l2;
// 需要和 l 同樣是 list<T> 型別,將整個複製
list <int> l = {1, 2, 3};
list <int> l{1, 2, 3};
// 也可以用傳統陣列的初始化方式來進行
初始化 list 中所有值:
l.assign(V, N);
設定 list 大小:
l.resize(N);
// V 是值,N 是大小
```
#### 複雜度
- 隨機存取極慢,只能遍歷 $O(N)$
- 增刪元素極快 $O(1)$
- 清除 $O(N)$
- size $O(1)$
- empty $O(1)$
:::spoiler * 是常用等級
#### 成員函式
- ***** `push_back()`
- ***** `pop_back()`
- ***** `push_front()`
- ***** `pop_front()`
- ***** `begin()`
- ***** `end()`
- **** `insert()`
- **** `erase()`
- **** `size()`
- **** `clear()`
- **** `front()`
- **** `back()`
- **** `splice()`
- **** `merge()`
- **** `remove()`
- **** `sort()`
- **** `reverse()`
- *** `empty()` 等同 `l.size() == 0`
- *** `assign()`
- *** `resize()`
- *** `emplace_back()` 像 `push_back()`
- *** `emplace_front()` 像 `push_front()`
- *** `emplace()` 像 `insert()`
- ** `rbegin()`
- ** `rend()`
- \* `unique()`
- \* `swap()`
- \* `cbegin()`
- \* `cend()`
- `crbegin()`
- `crend()`
:::
#### 這很麻煩,我不想寫使用範例
---
### forward_list (C++ 11)
:::spoiler 沒有用收起來
實用程度:$0$
在標頭檔 `<forward_list>` 中定義
這更難用,以 `single-linked-list` 實作而成
只能單向操作,顯然用 `vector` 更好,同樣不支援隨機存取,不要用他
==連 `size()` 都沒有是要玩啥==
#### 宣告:
```cpp
forward_list <T> fl(N, V);
// T 是資料型態
// N 是大小,不加預設為空
// V 是每一格的值,要有大小後才能加,不加預設為空或 0
forward_list <T> fl(it1, it2);
// it1、it2 是其它以 list 為基底的容器迭代器,左閉右開複製到 forward_list 中
forward_list <T> fl(fl2);
forward_list <T> fl{fl2};
forward_list <T> fl = fl2;
// 需要和 fl 同樣是 forward_list<T> 型別,將整個複製
forward_list <int> fl = {1, 2, 3};
forward_list <int> fl{1, 2, 3};
// 也可以用傳統陣列的初始化方式來進行
初始化 list 中所有值:
fl.assign(V, N);
設定 list 大小:
fl.resize(N);
// V 是值,N 是大小
```
#### 複雜度
- 隨機存取極慢,只能遍歷 $O(N)$
- 增刪元素極快 $O(1)$
- 清除 $O(N)$
- size $O(1)$
- empty $O(1)$
:::spoiler * 是常用等級
#### 成員函式
- ***** `push_front()`
- ***** `pop_front()`
- ***** `begin()`
- ***** `end()`
- **** `before_begin()`
- **** `insert_after()`
- **** `erase_after()`
- **** `clear()`
- **** `front()`
- **** `splice_after()`
- **** `merge()`
- **** `remove()`
- **** `sort()`
- **** `reverse()`
- **** `empty()`
- *** `assign()`
- *** `resize()`
- ** `emplace_after()` 像 `insert_after()`
- ** `emplace_front()` 像 `push_front()`
- \* `unique()`
- \* `swap()`
- \* `cbegin()`
- \* `cend()`
- \* `cbefore_begin()`
:::
#### 根本沒有用,不需要使用範例
---
### pair
實用程度: :star: :star: :star: :star:
在標頭檔 `<utility>` 中定義
`<algorithm>` 已經有 `#include <utility>`
因此也可以直接 `#include <algorithm>`
但其實自己寫一個 `struct` 可能有時也比 `pair` 更方便,兩者看個人偏好
#### 宣告:
```cpp
pair <T1, T2> p{a, b};
pair <T1,T2> p = {a, b};
pair <T1, T2> p(a, b);
pair <T1, T2> p = make_pair(a, b);
pair <T1, T2> p = {make_pair(a, b)};
// T1、T2 是資料型態
// 後面不打值預設為空或 0
pair <T1, T2> p1{p};
pair <T1,T2> p1 = {p};
pair <T1,T2> p1 = p;
pair <T1, T2> p1(p);
// 直接複製 p 到 p1,兩者資料型態要相同
```
#### 用法
- 用 `make_pair(a, b)` 來構造出一個 `pair` ,直接用 `{a, b}` 呼叫建構元也可以
- first 存取第一項的值
- second 存取第二項的值
#### 使用範例:sort cmp
他常搭配 `sort` 使用,但自定義比較要有 `cmp` 函式,下一章會提到
[傳送門在這](https://hackmd.io/yzwA-2lsTsepbJYTz2ZZWg#cmp-%E8%87%AA%E5%AE%9A%E7%BE%A9%E5%87%BD%E5%BC%8F)
---
### tuple (C++ 11)
:::spoiler 用 `struct` 啦!
實用程度: :star:
在標頭檔 `<tuple>` 中定義
其實要用 `tuple` (元組) 還不如用更多樣性的 `struct` (結構體) 所以常常被取代
#### 宣告:
```cpp
tuple <T1, T2, ..., TN> tpl(v1, v2, ..., vN);
tuple <T1, T2, ..., TN> tpl{v1, v2, ..., vN};
tuple <T1, T2, ..., TN> tpl = make_tuple(v1, v2, ..., vN);
// T1、T2 是資料型態
// 後面不打值預設為空或 0
tuple <T1, T2, ..., TN> tpl1{tpl};
tuple <T1, T2, ..., TN> tpl1 = {tpl};
tuple <T1, T2, ..., TN> tpl1 = tpl;
tuple <T1, T2, ..., TN> tpl1(tpl);
// 直接複製 tpl 到 tpl1,兩者資料型態要相同
```
#### 用法
- 用 `make_tuple(a, b, ...)` 來構造出一個 `tuple` ,直接用 `{a, b, ...}` 呼叫建構元也可以
- `get <x> (tpl)` 來存取第 x - 1 項的參照
- `tuple_element<x,decltype(tpl)>::type` 可以拿到第 x - 1 項的型別
- `tuple_size<decltype(tpl)>::value` 可以拿到 `tuple` 的大小
- 和 `pair` 一樣可以 `sort`
- 成員函式有 `swap()`
- 用全員函式 `tie(a, b, ...)` 可以將元素一一分配到 `tuple` 中
#### 我會直接用 `struct`,沒有使用範例
往後第兩章會提到
[傳送門在這](https://hackmd.io/@FDfrankie/textbook/%2F39vpPSkdRna66NwjcITQag)
:::success
特別的是:`tuple` 中的型別 `type` 可以是 `reference` 參照 (引用)
```cpp
int ref = 10;
tuple <int&> tpl(ref);
// 此時 int& 代表的是 ref 的別稱,也就是參照,當我更改 tpl 的第 1 格時,ref 同時被改動
get <0> (tpl) = 8;
// ref 也變成 8
```
:::
---
### array (C++ 11)
實用程度: :star: :star:
在標頭檔 `<array>` 中定義
他是將一維傳統陣列,以 `STL` 方式實作而成,一樣大小不可變
好處是可以在 `vector` 等其他容器內
#### 宣告:
```cpp
array <T, N> arr = {...T物件};
array <T, N> arr{...T物件};
// T 是資料型態,N 是大小,= 可打可不打
// 若 T 物件不足 N 個,沒指定的都默認是 0
array <T, N> arr = {};
array <T, N> arr{};
array <T, N> arr;
// 不打值預設為 0
要初始化陣列中所有值:
arr.fill(V);
// V 是值
```
#### 成員函式
:::spoiler * 是常用等級
- ***** `operator[]`
- **** `begin()`
- **** `end()`
- **** `fill()`
- *** `size()`
- *** `back()` 等同 `[arr.size() - 1]`
- *** `empty()` 等同 `arr.size() == 0`
- ** `front()` 等同 `[0]`
- ** `rbegin()`
- ** `rend()`
- \* `at()` 等同 `[]`
- \* `swap()`
- \* `cbegin()`
- \* `cend()`
- `crbegin()`
- `crend()`
- `data()`
:::
#### 跟傳統陣列很像,也是 `vector` 的下位版,就不特別介紹了
---
### vector
實用程度: :star: :star: :star: :star: :star:
在標頭檔 `<vector>` 中定義
`vector` 是傳統陣列的加強版,他可變大小,而且支援隨機存取,非常好用
可以直接用他取代傳統陣列,而且他能抓取的記憶體較傳統陣列來得多
#### 宣告:
```cpp
vector <T> vec(N, V);
// T 是資料型態
// N 是大小,不加預設為空
// V 是每一格的值,要有大小後才能加,不加預設為空或 0
vector <T> vec(it1, it2);
// it1、it2 是其它以 vector 為基底的容器迭代器,左閉右開複製到 vector 中
vector <T> vec(vec2);
vector <T> vec{vec2};
vector <T> vec = vec2;
// 需要和 vec 同樣是 vector<T> 型別,將整個複製
vector <int> vec = {1, 2, 3};
vector <int> vec{1, 2, 3};
// 也可以用傳統陣列的初始化方式來進行
初始化 vector 中所有值:
vec.assign(N, V);
設定 vector 大小:
vec.resize(N);
// V 是值,N 是大小
```
#### 複雜度
- 隨機存取快 $O(1)$
- 從尾端增刪元素快 $O(1)$
- 中間增刪元素極慢 $O(N)$
- 清除 $O(N)$
- size $O(1)$
- empty $O(1)$
:::spoiler * 是常用等級
#### 成員函式
- ***** `operator[]`
- ***** `push_back()`
- ***** `pop_back()`
- **** `begin()`
- **** `end()`
- **** `size()`
- **** `clear()`
- *** `empty()` 等同 `vec.size() == 0`
- *** `assign()`
- *** `resize()`
- *** `fill()`
- *** `back()` 等同 `[vec.size() - 1]`
- *** `emplace_back()` 常數較 `push_back()` 來得小一些
- ** `insert()`
- ** `erase()`
- ** `front()` 等同 `[0]`
- ** `rbegin()`
- ** `rend()`
- \* `swap()`
- \* `cbegin()`
- \* `cend()`
- \* `at()` 等同 `[]`
- `crbegin()`
- `crend()`
- `data()`
- `shrink_to_fit()`
- `emplace()` 很像 `insert`
:::
#### 使用範例
由小至大排序一串數字
```cpp=
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, t;
vector <int> vec;
cin >> n;
vec.clear();
for (int i = 0; i < n; i++) {
cin >> t;
vec.push_back(t);
}
sort(vec.begin(), vec.end());
for (int i = 0; i < vec.size(); i++)
cout << vec[i] << ' ';
cout << endl;
return 0;
}
```
### 2D vector
我要把這個單獨拿出來講,這就是 `STL` 包著另一個 `STL` 的例子,也是我們最常碰到的一個
他和傳統二維陣列一樣,只是每一列都是可變陣列 `vector`,而且也可加欄 `column`
十分好用,我們常拿他來建圖,而且有時他可能比傳統二維陣列還要省空間
矩陣運算時,他也可以在函式之間傳遞,不像傳統二維陣列一樣麻煩
[建圖](https://hackmd.io/@FDfrankie/textbook/%2Fcta0PHmaSq67rbnoDbGDEA)
#### 宣告
```cpp
vector < vector<T> > vec(N, vector<T>(K, V));
// T 是資料型態
// 後面的 vector<T> 若不打也是預設此 2D vector 的每一個 vector 為空
// N、K 是大小,不加預設為空
// V 是每一格的值,要有大小後才能加,不加預設為空或 0
初始化 2Dvector 中所有 vector:
vec.assign(N, vector<T>(K, V));
設定 2Dvector 大小:
vec.resize(N);
// V 是值,N K 是大小
```
---
### deque
實用程度: :star: :star: :star: :star: :star:
全名是: `double ended queue` 雙向佇列
在標頭檔 `<deque>` 中定義
`deque` 又是 `vector` 的加強版,他可雙向增刪,非常好用
但要小心他的常數比 `vector` 略高,不過通常題目不會卡這點小東西
#### 宣告:
```cpp
deque <T> dq(N, V);
// T 是資料型態
// N 是大小,不加預設為空
// V 是每一格的值,要有大小後才能加,不加預設為空或 0
deque <T> dq(it1, it2);
// it1、it2 是其它以 deque 為基底的容器迭代器,左閉右開複製到 deque 中
deque <T> dq(dq2);
deque <T> dq{dq2};
deque <T> dq = dq2;
// 需要和 dq 同樣是 deque<T> 型別,將整個複製
deque <int> dq = {1, 2, 3};
deque <int> dq{1, 2, 3};
// 也可以用傳統陣列的初始化方式來進行
初始化 deque 中所有值:
dq.assign(N, V);
設定 deque 大小:
dq.resize(N);
// V 是值,N 是大小
```
#### 複雜度
- 隨機存取快 $O(1)$
- 頭尾增刪元素快 $O(1)$
- 中間增刪元素極慢 $O(N)$
- 清除 $O(N)$
- size $O(1)$
- empty $O(1)$
#### 成員函式
:::spoiler * 是常用等級
- ***** `operator[]`
- ***** `push_back()`
- ***** `push_front()`
- ***** `pop_front()`
- ***** `pop_back()`
- **** `begin()`
- **** `end()`
- **** `size()`
- **** `clear()`
- *** `empty()` 等同 `dq.size() == 0`
- *** `assign()`
- *** `resize()`
- *** `fill()`
- *** `back()` 等同 `[dq.size() - 1]`
- *** `emplace_front()` 常數較 `push_front()` 來得小一些
- *** `emplace_back()` 常數較 `push_back()` 來得小一些
- ** `insert()`
- ** `erase()`
- ** `front()` 等同 `[0]`
- ** `rbegin()`
- ** `rend()`
- \* `swap()`
- \* `cbegin()`
- \* `cend()`
- \* `at()` 等同 `[]`
- `crbegin()`
- `crend()`
- `data()`
- `shrink_to_fit()`
- `emplace()` 很像 `insert`
:::
#### 使用範例
在原本數列中進行以下操作:
1. 前後插入
2. 前後刪除,並將刪除元素輸出 (若陣列為空視為無效操作)
3. 輸出陣列最前方或最後方元素
4. 隨機存取 (若超過陣列大小視為無效操作)
5. 刪除後方元素直到剩 `n` 位 (`n` `<=` 目前陣列大小)
6. 存取陣列大小
```cpp=
#include <iostream>
#include <deque>
using namespace std;
int main() {
int q, n;
string s;
cin >> n;
deque <int> dq(n);
for (int i = 0; i < dq.size(); i++)
cin >> dq[i];
cin >> q;
while (q--) {
cin >> s;
if (s == "push_back") {
cin >> n;
dq.push_back(n); // 插入後方
}
else if (s == "push_front") {
cin >> n;
dq.push_front(n); // 插入前方
}
else if (s == "pop_front" && !dq.empty()) {
cout << dq.front() << endl;
// cout << dq[0] << endl;
dq.pop_front(); // 前方刪除
}
else if (s == "pop_back" && !dq.empty()) {
cout << dq.back() << endl;
// cout << dq[dq.size() - 1] << endl;
dq.pop_back(); // 後方刪除
}
else if (s == "resize") {
cin >> n;
dq.resize(n);
}
else if (s == "at") {
cin >> n;
if (n < dq.size() && n >= 0)
cout << dq[n] << endl;
}
else if (s == "size")
cout << dq.size() << endl;
else if (s == "front" && !dq.empty())
cout << dq.front() << endl; // 前方元素
// cout << dq[0] << endl;
else if (s == "back" && !dq.empty())
cout << dq.back() << endl; // 後方元素
// cout << dq[dq.size() - 1] << endl;
}
return 0;
}
```
---
### stack
實用程度: :star:
中文名是堆疊、棧、堆棧
他的重點就是 `FILO,first in last out` 或 `LIFO,last in first out`
想像一罐洋芋片,一定得從最上面拿起對吧
`DFS` 爆搜常用到概念,但還是被 `vector` 取代
在標頭檔 `<stack>` 中定義
`stack` 是 `vector` 的弱化版,不支援隨機存取,而且還用 `deque` 實作,常數大,不如用 `vector`
:::danger
注意:沒有 `clear()` 喔
清除方式用遍歷
```cpp=
stack <int> sk;
while (!sk.empty()) sk.pop();
// while (sk.size()) sk.pop(); 也可以
```
:::
#### 圖示:
![](https://i.imgur.com/LeXikLE.png)
#### 宣告:
```cpp
stack < T, C<T> > sk;
// T 是資料型態
// C<T> 是指以 C<T> 為基底,預設 deque<T>
// C<T> 要能支援 back(), push_back(), pop_back(), empty(), size(),能放 vector, deque, list
stack < T, C<T> > sk(sk2);
stack < T, C<T> > sk{sk2};
// 需要和 sk 同樣以C <T> 為基底的型別,將整個複製
stack < T, C<T> > sk = sk2;
stack < T, C<T> > sk = {sk2};
// 需要同樣是 stack < T, C<T> > 型別,才能將整個複製
stack <int> sk = {1, 2, 3};
stack <int> sk{1, 2, 3};
// 也可以用傳統陣列的初始化方式來進行
```
#### 複雜度
- 棧頂增刪元素 $O(1)$
- 讀取棧頂元素 $O(1)$
- size $O(1)$
- empty $O(1)$
#### 成員函式
:::spoiler * 是常用等級
- ***** `push()`
- ***** `pop()`
- ***** `top()`
- **** `size()`
- *** `empty()` 等同 `sk.size() == 0`
- \* `emplace()` 等同 `push`
- `swap()`
:::
#### 使用範例
支援下列操作:
1. 將元素推入陣列後方
2. 刪除陣列最後方元素,並將其輸出 (若陣列為空視為無效操作)
3. 輸出陣列最後方的值 (若陣列為空視為無效操作)
4. 輸出陣列大小
```cpp=
#include <iostream>
#include <stack>
using namespace std;
int main() {
int t, n;
string s;
cin >> t;
stack <int> sk;
while (t--) {
cin >> s;
if (s == "push") {
cin >> n;
sk.push(n);
}
else if (s == "pop" && !sk.empty()) {
cout << sk.top() << endl;
sk.pop();
}
else if (s == "back" && !sk.empty())
cout << sk.top() << endl;
else if (s == "size")
cout << sk.size() << endl;
}
return 0;
}
```
---
### queue
實用程度: :star: :star:
中文名是 (單向) 佇列
他的重點就是 `FIFO,first in first out` 或 `LILO,last in last out`
想像排隊的隊伍,先到的一定先買東西對吧,但絕對沒有沒品的人插隊!
`BFS` 爆搜常用
在標頭檔 `<queue>` 中定義
`queue` 是 `deque` 的弱化版,不支援隨機存取
`queue` 是用 `deque` 實作的,和 `stack` 一樣
:::warning
其實因為 `queue` 和 `stack` 一樣是用 `deque` 來實作的
因此操作上比直接用 `deque` 還要多了一步,因此可能會慢 `deque` 大概 $10\%$ 左右
不過因為他們可讀性很高,因此寫程式若沒有追求極致速度時,還是會選擇使用他們
順帶一提,不知為何 `stack` 常被 `vector` 取代,但 `queue` 比較少被 `deque` 取代
(所以我才給 `stack` 一星,`queue` 兩星)
:::
:::danger
注意:沒有 `clear()` 喔
清除方式用遍歷
```cpp=
queue <int> q;
while (!q.empty()) q.pop();
// while (q.size()) q.pop(); 也可以
```
:::
#### 圖示:
![](https://i.imgur.com/sjUSyd0.png)
#### 宣告:
```cpp
queue < T, C<T> > q;
// T 是資料型態
// C<T> 是指以 C<T> 為基底,預設 deque<T>
// C<T> 要能支援 front(), back(), push_back(), pop_front(), empty(), size(),能放 deque, list
queue < T, C<T> > q(q2);
queue < T, C<T> > q{q2};
// 需要和 q 同樣以 C<T> 為底的型別,將整個複製
queue < T, C<T> > q = q2;
queue < T, C<T> > q = {q2};
// 需要同樣是 queue < T, C<T> > 型別,才能將整個複製
queue <T> q{it1, it2}; // C++23,我也是查了才知道,新用法,compiler不夠新會 CE
// it1、it2 是其它以 C<T> 為基底的容器迭代器,左閉右開複製到 queue 中
```
:::danger
不能用 $\{1,\ 2,\ 3\}$ 這種傳統陣列的大括號來初始化喔
:::
#### 複雜度
- 前出後推 $O(1)$
- 讀取頭尾元素 $O(1)$
- size $O(1)$
- empty $O(1)$
#### 成員函式
:::spoiler * 是常用等級
- ***** `push()`
- ***** `pop()`
- ***** `front()`
- **** `back()`
- **** `size()`
- *** `empty()` 等同 `q.size() == 0`
- \* `emplace()` 等同 `push`
- `swap()`
:::
#### 使用範例
支援下列操作:
1. 將元素推入陣列後方
2. 刪除陣列最前方元素,並將其輸出 (若陣列為空視為無效操作)
3. 輸出陣列最後方的值 (若陣列為空視為無效操作)
4. 輸出陣列大小
```cpp=
#include <iostream>
#include <queue>
using namespace std;
int main() {
int t, n;
string s;
cin >> t;
queue <int> q;
while (t--) {
cin >> s;
if (s == "push") {
cin >> n;
q.push(n);
}
else if (s == "pop" && !q.empty()) {
cout << q.front() << endl;
q.pop();
}
else if (s == "back" && !q.empty())
cout << q.back() << endl;
else if (s == "front" && !q.empty())
cout << q.front() << endl;
else if (s == "size")
cout << q.size() << endl;
}
return 0;
}
```
---
### priority_queue
實用程度: :star: :star: :star:
他叫優先佇列,是由一個很猛的東西: `heap` 所實作而成,如果只要求極值非他莫屬
**在標頭檔 `<queue>` 中定義**
~~沒有 <priority_queue> 啦~~
同樣不支援隨機存取
頂端元素預設最大值
若要改成最小值應該在宣告時將 `cmp` 格從預設的 `less<T>` 改成 `greater<T>`
:::danger
注意:沒有 `clear()` 喔
清除方式用遍歷
```cpp=
priority_queue <int> pq;
while (!pq.empty()) pq.pop();
// while (pq.size()) pq.pop(); 也可以
```
:::
#### 宣告:
```cpp
priority_queue <T, C<T>, cmp> pq;
// T 是資料型態
// C<T>、cmp 可以不寫
// C<T> 是指以 C<T> 為基底,預設 vector<T>
// C<T> 要能支援 front(), push_back(), pop_back(), empty(), size(),能放 deque, list
// cmp 是一個 class 或 struct 也可以,C<T> 一定要先寫才有這格,預設 less<T>,他的排序是反向的
// less<T> 在 <functional> 中定義
priority_queue <T> q{it1, it2};
// it1、it2 是同樣裝 T 的容器迭代器,左閉右開丟入 priority_queue 中
priority_queue <T, C<T>, cmp> pq(pq2);
priority_queue <T, C<T>, cmp> pq{pq2};
priority_queue <T, C<T>, cmp> pq = {pq2};
priority_queue <T, C<T>, cmp> pq = pq2;
// 需要和 pq 同樣以 C<T> 為底的型別,而且同樣以 cmp 為基礎作優先排序,將整個複製
priority_queue <T, C<T>, cmp> pq {cmp(),V};
priority_queue <T, C<T>, cmp> pq (cmp(),V);
// cmp() 必打,而且必和 pq 中使用的 cmp 相同,V 是 C<T> 型別
```
#### cmp寫法
:::spoiler 好毒藏起來
用 `operator overloading` 重載小括號,不用 `cmp` 直接重載小於也可以
```cpp=
struct cmp {
bool operator() (const int a, const int b) {
return a > b; //最小值需反向定義,使用 >
}
};
class cmp {
public:
bool operator() (const int a, const int b) {
return a > b; //最小值需反向定義,使用 >
}
};
priority_queue <int, vector<int>, cmp> pq(cmp, v);
/*-----------------------------------------------------------------------*/
bool cmp(const int a, const int b) {return a > b;}
priority_queue <int, vector<int>, decltype(cmp)*> pq(cmp, v);
priority_queue <int, vector<int>, decltype(&cmp)> pq(&cmp, v);
```
:::
#### 複雜度
- 推入、刪除頂端元素 $O(lgN)$
- 讀取頂端元素 $O(1)$
- size $O(1)$
- empty $O(1)$
#### 成員函式
:::spoiler * 是常用等級
- ***** `push()`
- ***** `pop()`
- ***** `top()`
- **** `size()`
- *** `empty()` 等同 `pq.size() == 0`
- \* `emplace()` 等同 `push`
- `swap()`
:::
#### 使用範例
達成以下操作:
1. 將元素加入陣列中
2. 輸出陣列中最大值 (若陣列為空視為無效操作)
3. 輸出陣列最大值並將其刪除 (若陣列為空視為無效操作)
4. 輸出陣列大小
```cpp=
#include <iostream>
#include <queue>
using namespace std;
int main() {
int t, n;
string s;
priority_queue <int> pq;
cin >> t;
while (t--) {
cin >> s;
if (s == "push") {
cin >> n;
pq.push(n);
}
else if (s == "pop" && !pq.empty()) {
cout << pq.top() << endl;
pq.pop();
}
else if (s == "top" && !pq.empty())
cout << pq.top() << endl;
else if (s == "size")
cout << pq.size() << endl;
}
return 0;
}
```
---
### set
實用程度: :star: :star: :star:
因為和 `map` 功能相同,因此很常被 `map` 取代
詳細的留到 `map` 說明
在標頭檔 `<set>` 中定義,不支援隨機存取
==**常數極大!!慎用!**==
#### 宣告:
```cpp
set <T, cmp> st;
// T 是資料型態
// cmp 是一個 class,struct 也可以,預設 less<T>
// less<T> 在 <functional> 中定義
set <T> st{it1, it2};
set <T> st = {it1, it2};
// it1、it2 是同樣裝 T 的容器迭代器,左閉右開丟入 set 中
// 但好像不能有 cmp 要不然會 RE,我不知道為何
set <int> st = {1, 2, 3};
set <int> st{1, 2, 3};
// 也可以用傳統陣列的初始化方式來進行
// 但好像不能有 cmp 要不然會 RE,我不知道為何
set <T, cmp> st(st2);
set <T, cmp> st{st2};
set <T, cmp> st = {st2};
set <T, cmp> st = st2;
// 需要裝 T,而且同樣以 cmp 為基礎作優先排序,將整個複製
```
#### cmp寫法
:::spoiler 好毒藏起來
用 `operator overloading` 重載小括號,不用 `cmp` 直接重載小於也可以
```cpp=
struct cmp {
bool operator() (const int a, const int b) {
return a < b;
}
};
class cmp {
public:
bool operator() (const int a, const int b) {
return a < b;
}
};
set <int, cmp> st(v.begin(), v.end());
/*-----------------------------------------------------------------------*/
bool cmp(const int a, const int b) {return a > b;}
set <int, decltype(cmp)*> st(v.begin(), v.end());
set <int, decltype(&cmp)> st(v.begin(), v.end());
```
:::
#### 複雜度
- 插入、刪除元素 $O(lgN)$
- 二分搜 $O(lgN)$
- size $O(1)$
- empty $O(1)$
#### 成員函式
:::spoiler * 是常用等級
- ***** `insert()`
- **** `erase()`
- **** `begin()`
- **** `end()`
- **** `size()`
- **** `find()`
- **** `lower_bound()`
- **** `upper_bound()`
- *** `count()` 在 `set` 中等同 `find(key) != st.end()`
- *** `empty()` 等同 `st.size() == 0`
- *** `clear()`
- ** `rbegin()`
- ** `rend()`
- \* `equal_range()`
- \* `emplace()` 很像 `insert`
- \* `cbegin()`
- \* `cend()`
- `crbegin()`
- `crend()`
- `swap()`
:::
#### 使用範例
陣列中不能有重複元素,若重複加入則視為無效操作
刪除不存在的元素亦視為無效操作
當查找第一個大於等於或大於目標值時,若目標值大於陣列中最大值,則輸出 `2147483647 (INT_MAX)`
支援下列操作:
1. 輸出最大值、最小值 (若陣列為空視為無效操作)
2. 刪除陣列中元素
3. 在陣列中插入元素
4. 刪除最大、最小值 (若陣列為空視為無效操作)
5. 輸出陣列大小
6. 查尋有無元素,若有,輸出 `true`;否則,輸出 `false`
7. 找到第一個大於等於目標且存在於陣列中的值 (若陣列為空視為無效操作)
8. 找到第一個大於目標且存在於陣列中的值 (若陣列為空視為無效操作)
9. 編歷陣列
```cpp=
#include <iostream>
#include <set>
#include <climits> // INT_MAX
using namespace std;
int main() {
int t, n;
string s;
cin >> t;
set <int> st;
while (t--) {
cin >> s;
if (s == "min" && !st.empty())
cout << *st.begin() << endl; // 最小值
else if (s == "max" && !st.empty()) // 最大值
cout << *st.rbegin() << endl;
else if (s == "erase") {
cin >> n;
st.erase(n);
}
else if (s == "erase_max" && !st.empty()) {
cout << *st.rbegin() << endl;
st.erase(prev(st.end())); // 刪除最大值
// st.erase(st.rbegin().base()) << endl;
}
else if (s == "erase_min" && !st.empty()) {
cout << *st.begin() << endl;
st.erase(st.begin()); // 刪除最小值
}
else if (s == "insert") {
cin >> n;
st.insert(n);
}
else if (s == "size")
cout << st.size() << endl;
else if (s == "find") {
cin >> n;
cout << st.count(n) << endl;
/*if (st.find(n) == st.end())
cout << "false\n";
else
cout << "true\n";*/
}
else if (s == "lower_bound") {
cin >> n;
auto it = st.lower_bound(n);
// set<int>::iterator = st.lower_bound(n);
if (it == st.end())
cout << INT_MAX << endl;
else
cout << *it << endl;
// 第一個大於等於 n 的值
}
else if (s == "upper_bound") {
cin >> n;
auto it = st.upper_bound(n);
// set<int>::iterator = st.upper_bound(n);
if (it == st.end())
cout << INT_MAX << endl;
else
cout << *it << endl;
// 第一個大於 n 的值
}
else if (s == "traversal") {
for (int i:st)
cout << i << endl;
/* for (auto it = st.begin(); it != st.end(); it++)
cout << *it << endl;*/
/* for (set<int>::iterator it = st.begin(); it != st.end(); it++)
cout << *it << endl;*/
// it++ 等價於 it = next(it);
}
}
return 0;
}
```
---
### map
實用程度: :star: :star: :star: :star:
在標頭檔 `<map>` 中定義,不支援隨機存取,只能以遍歷方式來走訪每一個節點
不論 `set` 還是 `map` 都是用紅黑邊樹來實作,紅黑邊樹是一個自平衡二元樹
相較於 `set` 只有 `key` ,`map` 是由一對 `key, value` 所組成
他不像 `vector` 等容器一般是連續記憶體,他的記憶體是跳著的,因此不能隨機存取
==**常數極大!!慎用!!**==
因為紅黑邊樹是自平衡二元樹,大家可以去看,他和 `AVL tree` 一樣都是以自旋 `rotate` 的方式來維護他的規則,因此,旋轉的次數、指標的操作都是增加常數的元凶
>key:鍵值 (在 `set`、`map` 中唯一)
>value: 值 (每一個 `key` 所對應到的值)
#### 宣告:
```cpp
map <T1, T2, cmp> m;
// T1 是 key 的資料型態,T2 是 value 的資料型態
// cmp 是一個 class,struct 也可以,預設 less<T>
// less<T> 在 <functional> 中定義
map <T1, T2> m{it1, it2};
map <T1, T2> m = {it1, it2};
// it1、it2 是同樣裝 pair<T1, T2> 的容器迭代器,左閉右開丟入 map 中
// 但好像不能有 cmp 要不然會 RE ,我不知道為何
map <int, int> m = {{1, 2},{3, 4}};
map <int, int> m{{1, 2},{3, 4}};
// 也可以用傳統二維陣列的初始化方式來進行
// 但好像不能有 cmp 要不然會 RE,我不知道為何
map <T1, T2, cmp> m(m2);
map <T1, T2, cmp> m{m2};
map <T1, T2, cmp> m = {m2};
map <T1, T2, cmp> m = m2;
// 需要裝 T,而且同樣以 cmp 為基礎作優先排序,將整個複製
```
#### cmp寫法
:::spoiler 好毒藏起來
用 `operator overloading` 重載小括號,不用 `cmp` 直接重載小於也可以
```cpp=
struct cmp {
bool operator() (const int a, const int b) {
return a < b;
}
};
class cmp {
public:
bool operator() (const int a, const int b) {
return a < b;
}
};
map <int, string, cmp> m(v.begin(), v.end());
/*-----------------------------------------------------------------------*/
bool cmp(const int a, const int b) {return a > b;}
map <int, string, decltype(cmp)*> m(v.begin(), v.end());
map <int, string, decltype(&cmp)> m(v.begin(), v.end());
```
:::
#### 複雜度
- 插入、刪除元素 $O(lgN)$
- 二分搜 $O(lgN)$
- size $O(1)$
- empty $O(1)$
#### 成員函式
:::spoiler * 是常用等級
- ***** `operator[]`
- **** `erase()`
- **** `begin()`
- **** `end()`
- **** `size()`
- **** `find()`
- **** `lower_bound()`
- **** `upper_bound()`
- *** `insert()` 等同 `m[key] = value`
- *** `count()` 在 `map` 中等同 `mp.find(key) != mp.end()`
- *** `empty()` 等同 `mp.size() == 0`
- *** `clear()`
- ** `equal_range()`
- ** `rbegin()`
- ** `rend()`
- \* `emplace()` 很像 `insert`
- \* `at()` 和 `[]` 相同
- \* `cbegin()`
- \* `cend()`
- `crbegin()`
- `crend()`
- `swap()`
:::
#### 使用範例
將出現的鍵值對加入 `map` 中
若值已存在,則將鍵值所對應的數加上
若目標大於等於或大於陣列之最大值,則輸出 `2147483647 (INT_MAX)`
註:此 `map` 代表一個編隊,編隊大小即為編號之數量
註:`key` 代表編碼、`val` 代表人數
支援以下操作:
1. 輸出編碼最大值、最小值對應之人數 (若無任何人視為無效操作)
2. 刪除陣列中編碼,代表廢止此編碼之使用,同時輸出此編碼廢止前之人數 (若代碼不存在視為無效操作,並輸出 `-2147483648 (INT_MIN)` )
3. 在陣列中插入編碼 (若存在則將人數累加到此編碼中)
4. 刪除最大編碼、最小編碼 (若無人視為無效操作)
5. 輸出編隊大小
6. 查尋有無此編號,若有,輸出 `true`;否則,輸出 `false`
7. 找到第一個大於等於目標且存在於陣列中的編碼,並輸出人數 (若陣列為空視為無效操作)
8. 找到第一個大於目標且存在於陣列中的編碼,並輸出人數 (若陣列為空視為無效操作)
9. 查尋此編號之對應人數,若無人輸出 $0$
10. 遍歷整個編隊 (輸出編號 人數)
```cpp=
#include <iostream>
#include <map>
#include <climits> // INT_MAX INT_MIN
using namespace std;
int main() {
int t, key, val;
string s;
map <int, int> m;
cin >> t;
while (t--) {
cin >> s;
if (s == "insert") {
cin >> key >> val;
m[key] += val;
/*auto it = m.find(key);
// map<int, int>::iterator it = m.find(key);
if (it == m.end())
m.insert(make_pair(key, val));
else
it->second += val;*/
}
else if (s == "erase") {
cin >> key;
if (m.find(key) == m.end())
cout << INT_MIN << endl;
else {
cout << m[key] << endl;
m.erase(key);
}
}
else if (s == "size")
cout << m.size() << endl;
else if (s == "max" && !m.empty())
cout << m.rbegin()->second << endl; // 最大值
else if (s == "min" && !m.empty())
cout << m.begin()->second << endl; // 最小值
else if (s == "erase_max" && !m.empty())
m.erase(prev(m.end())); // 刪除最大值
// m.erase(m.rbegin().base());
else if (s == "erase_min" && !m.empty())
m.erase(m.begin()); // 刪除最小值
else if (s == "find") {
cin >> key;
auto it = m.find(key);
// map<int, int>::iterator it = m.find(key);
if (it == m.end())
cout << "false\n";
else
cout << "true\n";
}
else if (s == "at") {
cin >> key;
auto it = m.find(key);
if (it == m.end())
cout << 0 << endl;
else
cout << it->second << endl;
// cout << m[key] << endl;
}
else if (s == "lower_bound") {
cin >> key;
auto it = m.lower_bound(key);
// map<int, int>::iterator it = m.lower_bound(key);
if (it == m.end())
cout << INT_MAX << endl;
else
cout << it->second << endl;
}
else if (s == "upper_bound") {
cin >> key;
auto it = m.upper_bound(key);
// map<int, int>::iterator it = m.lower_bound(key);
if (it == m.end())
cout << INT_MAX << endl;
else
cout << it->second << endl;
}
else if (s == "traversal") {
for (auto i:m) // for (pair <const int, int> i:m)
cout << i.first << ' ' << i.second << endl;
/* for (auto it = m.begin(); it != m.end(); it++)
cout << it->first << ' ' << it->second << endl;*/
/* for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)
cout << it->first << ' ' << it->second << endl;*/
// it++ 等價於 it = next(it);
}
}
return 0;
}
```
---
### multiset
:::spoiler 這些都是 `map`、`set` 的延伸,想看再打開
實用程度: :star: :star:
**在標頭檔 `<set>` 中定義**
~~沒有 `<multiset>`~~
我用過不超過十次,這要在特定題目中才有用
和 `set` 特性完全相同,只是他的 `key` 可以重覆而已
此時,`count` 所代表的再也不是 $0\ /\ 1$ 的 `int` 而是其中的數量
`eqal_range()` 在這裡也總算是有點用處,提升至 ***
`lower_bound()` 按照他的定義會回傳「第一個」大於等於的位置
`upper_bound()`則回傳「第一個」大於的位置
`find`若找到回傳「最前面」的那一個位置
因此若此時再寫 `mst.erase(K)` 那會變成將 `set` 中所有的 $K$ 全部刪除,若只要刪除一個應該寫:
`mst.erase(mst.find(K))`
#### 宣告
```cpp
multiset <T, cmp> mst;
```
#### 使用範例
**APCS** 有出過這個 `STL container` 的應用
[傳送門在這](https://hackmd.io/FNpFIaqeQ8WFg45mCddZAw#P4)
:::
---
### multimap
:::spoiler 這些都是 `map`、`set` 的延伸,想看再打開
實用程度: $*$ *給小星星* (沒啥大用)
**在標頭檔 `<map>` 中定義**
~~沒有 `<multimap>`~~
我只用過一到兩次,這要在很特定題目中才有用
和 `map` 不一樣的地方就和 `multiset` 一樣,不再贅述
#### 宣告
```cpp
multimap <T1, T2, cmp> mm;
```
:::
---
### unordered_set (C++ 11)
:::spoiler 這些都是 `map`、`set` 的延伸,想看再打開
實用程度: :star: :star:
**在標頭檔 `<unordered_set>` 中定義**
~~跟 `multiset` 不一樣,不是在 `<set>` 喔~~
在不需要排序的情形下才能用這個
比較少用,因為 `set` 題目很少壓這個
用哈希表 `hash table` 實作,而不是紅黑邊樹,因此才在不同標頭檔定義
最差插入、刪除複雜度 $O(n)$ 是因為 `hash table` 建得不太好的原因
有時還可能比基本款還慢呢!
函式比 `set` 再多一點,因為他可以操縱 `hash`,成員函式中有一些專門用來處理 `hash` 的東東
==但要注意:有時候內鍵 `hash` 不支援一些複雜的鍵值型別,例如 `vector < pair<int, int> >`==
==如果寫進去的話會吃 `CE`,這時候就乖乖用回原本的 `set` 或 `map` 吧==
#### 宣告
```cpp
unordered_set <T, Hash, KeyEqual> ust;
// hash 預設用 hash<T>
// KeyEqual 預設用 equal_to<T>
// 兩者都在 <functional> 中定義
```
#### 複雜度
- 插入、刪除 「平均」$O(1)$,最佳 $O(1)$,最差 $O(n)$
- 查詢 $O(1)$,`hash table` 的特性
:::
---
### unordered_map (C++ 11)
:::spoiler 這些都是 `map`、`set` 的延伸,想看再打開
實用程度: :star: :star: :star:
**在標頭檔 `<unordered_map>` 中定義**
~~跟 `multimap` 不一樣,不是在 `<map>` 喔~~
有部份機車題目會壓這個 $O(1)$
和 `map` 不一樣的地方就和 `unordered_set` 一樣,不再贅述
#### 宣告
```cpp
unordered_map <T1, T2, Hash, KeyEqual> um;
// hash 預設用 hash<T1>
// KeyEqual 預設用 equal_to<T1>
// 兩者都在 <functional> 中定義
```
#### 複雜度
- 插入、刪除 「平均」$O(1)$,最佳 $O(1)$,最差 $O(n)$
- 查詢 $O(1)$,`hash table` 的特性
:::
---
### unordered_multiset (C++ 11)
:::spoiler 這些都是 `map`、`set` 的延伸,想看再打開
實用程度: $0$
**在標頭檔 `<unordered_set>` 中定義**
~~跟 `unordered_set` 一樣,不是在 `<set>` ~~
他同樣也是用 `hash table` 來實作,因此在 `<unordered_set>` 中定義
我根本沒用過
:::
---
### unordered_multimap (C++ 11)
:::spoiler 這些都是 `map`、`set` 的延伸,想看再打開
實用程度: $0$
**在標頭檔 `<unordered_map>` 中定義**
~~跟 `unordered_map` 一樣,不是在 `<map>` ~~
他同樣也是用 `hash table` 來實作,因此在 `<unordered_map>` 中定義
我根本沒用過
:::
---
## 題目練習
### 初階班
`string + stringstream`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a092
`vector + pair + cmp`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a582
雖然出現了一個很恐怖的名詞:`discretization` 離散化,但其實就是 `vector + pair + cmp`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a090
`vector + pair + cmp`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a391
`vector + pair + cmp`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a589
`vector + pair + cmp`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a895
`queue + stack + priority_queue`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a891
`map`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a890
### 進階班
`map`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a773
`map`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a349
`set`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a466
`priority_queue`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a319
`stack`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a167
`stack`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a804
`priority_queue`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a811
`bitset`
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a581
練習 -- 複合題型
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a750
練習 -- 複合題型
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a787
練習 -- 複合題型
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a704
練習 -- 複合題型
https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a865
練習 -- **APCS** 搭配演算法使用
https://zerojudge.tw/ShowProblem?problemid=j608
---
## 製作者 / 協作者:
> 製作者:
> >
> > [color=#000000] [name= frankie 阮豐翗 (代課)] [time=Sat, Dec 10, 2022 10:37 PM]
> >
> 協作者:
> > [color=#FF0000] [name= 第 11 屆進階助教 chrislaiisme 賴宇弘]
> > [color=#FF0000] [name= 第 11 屆進階教學 william 郭勝威 :poop: ]
> >
> 引用講義:
> > [color=#1E1ED2] [name= 第 11 屆初階教學 samsonjaw 趙炫翔]
> >
> 學長們的講義 (9th):
> > [color=#FFFF00]https://hackmd.io/@konchin/book/%2FuX4pl05vRpKAgiuR0XHi_g