【C++】競程筆記(資料結構:map)
===
[TOC]
程式碼範例參考:NTUCPC Guide,此筆記僅為個人學習用途。
簡介
---
map 是 C++ STL 裡面的一個容器,跟 set 一樣都是關聯式容器(Associative Container),他用鍵值對(Key & Value)的方式來存資料,並自動依 key 的順序進行排序(預設為升序)。
可以想成 Python 的 dictionary,但有點不一樣。
鍵值對就是 key 對應一個 value,如:`{1, "apple"}`,1 這個 key 會對應到 apple,輸入 1 時就會輸出 apple。
map 的底層結構也是基於紅黑樹的自平衡二元搜尋樹。
### 特性
- 唯一性:
- key 不能重複。
- 元素型態:
- `pair<const Key, T>`,key 為常數。
- 不支援隨機存取:
- 只能透過 iterator 遍歷容器。
- 雙向迭代器:
- 可向前向後遍歷元素,表示可用 `rbegin()` 跟 `rend()` 反向迭代器。
### 語法(Syntax)
```
map<key_type, value_type, comp> m;
```
key_type:key 的資料型態。
value_type:value 的資料型態。
comp:比較函式,改降序一樣用 `greater<key_T>`,key_T 是 key 的資料型態。
m:map 的名稱。
### 標頭檔
使用前須引入標頭檔 `<map>`
### 基本操作
- 存取操作:
- `[key]`:如 `m["apple"]`。
- `at(key)`:與 `[key]` 用法一樣,只不過這會做邊界檢查,較為安全。
- 插入操作:
- `insert({key, val})`:插入元素(pair)。
- `emplace(k, v)`:比 `insert` 效能更高。
- `emplace_hint(pos, k, v)`:pos 放迭代器,效能比 `emplace` 更高一點。
- 搜尋操作:
- `find(key)`:回傳指向該元素的 `iterator`,沒找到元素就會回傳 `end()`。
- `count(key)`:回傳該 key 是否存在(0 不存在;1 存在)。
- `lower_bound(key)`:回傳第一個 key >= 指定值的 iterator。
- `upper_bound(key)`:回傳第一個 key > 指定值的 iterator。
- 刪除操作:
- `erase(key)`:刪除指定 key 的元素。
- `erase(iterator)`:刪除指定位置的元素。
- `erase(first, last)`:刪除範圍內所有元素。
遍歷操作的時間複雜度為 $O(n)$ ,以上所有操作皆為 $O(log n)$ 。
- 元素個數:`size()`。
- 檢查容器是否為空:`empty()`。
- 清除所有元素:`clear()`。
操作範例
---
### 建立 map
以下遍歷方式是用 range-based for loop:
key:p.first,取得 key。
value:p.second,取得 value。
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
map <int, string> m1;
map <int, string> m2 ={
{1, "apple"},
{2, "banana"},
};
for (auto& p : m2){
cout << p.first << " " << p.second << "\n";
}
return 0;
}
```
Output:
```
1 apple
2 banana
```
### 存取操作
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
map <int, string> m = {
{1, "apple"},
};
cout << "using [] : " << m[1] << '\n';
cout << "using at() : " << m.at(1);
return 0;
}
```
Output:
```
using [] : apple
using at() : apple
```
### 插入操作
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
map <int, string> m = {
{1, "apple"},
};
m.insert({2, "banana"});
m.emplace(3, "cherry");
m.emplace_hint(m.end(), 4, "date");
for (auto& p : m){
cout << p.first << " " << p.second << '\n';
}
return 0;
}
```
Output:
```
1 apple
2 banana
3 cherry
4 date
```
### 搜尋操作
`map <int, string>` 的 iterator 是指向 `pair <const int, string>` 這個指標物件,所以用 `it->second` or `it->first`。
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
map<int, string> students;
students[101] = "Luke";
students[103] = "醜男";
students[105] = "王奕翔";
int key = 103;
auto it = students.find(key);
if (it != students.end()) {
cout << "find(): Found student ID " << key << " -> " << it->second << endl;
} else {
cout << "find(): Student ID " << key << " not found" << endl;
}
if (students.count(key)) {
cout << "count(): Student ID " << key << " exists" << endl;
} else {
cout << "count(): Student ID " << key << " does not exist" << endl;
}
auto lb = students.lower_bound(102);
if (lb != students.end()) {
cout << "lower_bound(102): Found student ID " << lb->first << " -> " << lb->second << endl;
} else {
cout << "lower_bound(102): No such student ID >= 102" << endl;
}
auto ub = students.upper_bound(103);
if (ub != students.end()) {
cout << "upper_bound(103): Found student ID " << ub->first << " -> " << ub->second << endl;
} else {
cout << "upper_bound(103): No such student ID > 103" << endl;
}
return 0;
}
```
Output:
```
find(): Found student ID 103 -> 醜男
count(): Student ID 103 exists
lower_bound(102): Found student ID 103 -> 醜男
upper_bound(103): Found student ID 105 -> 王奕翔
```
### 刪除操作
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
map<int, string> students;
students[101] = "王奕翔";
students[102] = "LukeTseng";
students[103] = "歐金金";
students[104] = "白冰冰";
cout << "Original Data :\n";
for (const auto& pair : students)
cout << pair.first << " -> " << pair.second << '\n';
students.erase(102);
auto it = students.find(103);
if (it != students.end())
students.erase(it);
it = students.begin();
auto last = students.end();
--last;
students.erase(it, last);
cout << "\nDelete after :\n";
for (const auto& pair : students)
cout << pair.first << " -> " << pair.second << '\n';
return 0;
}
```
Output:
```
Original Data :
101 -> 王奕翔
102 -> LukeTseng
103 -> 歐金金
104 -> 白冰冰
Delete after :
104 -> 白冰冰
```
### 遍歷方式
1. range-based for loop
2. for loop
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
map<string, int> m;
m["apple"] = 3;
m["banana"] = 5;
m["orange"] = 2;
for (const auto& pair : m) {
// pair 是一個 key-value 對,pair.first 是 key,pair.second 是 value
cout << pair.first << " : " << pair.second << endl;
}
cout << "---------------" << endl;
for (map<string, int>::iterator it = m.begin(); it != m.end(); ++it) {
// it 是一個指向 pair 的 iterator,要用 -> 取值
cout << it->first << " : " << it->second << endl;
}
return 0;
}
```
Output:
```
apple : 3
banana : 5
orange : 2
---------------
apple : 3
banana : 5
orange : 2
```
multimap
---
就跟 set 有 multiset 一樣,multimap 可以有重複的 key。
用法也與 map 一樣。
語法:
```
multimap <key_type, value_type, comp> mm;
```
習題練習
---
### Uva 10226 Hardwood Species
Source:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=1167
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=d492
單字:
- botanical (adj.):植物(學)的;和植物(學)有關的。
- broad leaves 指的是寬大的葉子。
- dormant (adj.):蟄伏的,沉睡的,休眠的。
- certain:某、某些、某個。
- oak (n.):橡木。
- conifer (n.):針葉樹。
- cedar (n.):雪松。
- fir (n.):冷杉、樅。
- hemlock (n.):毒參。
- pine (n.):松樹。
- spruce (n.):雲杉。
- cypress (n.):柏樹。
- lumber (v.)(n.):緩慢地移動;木材、木料。
`cin` 不會吃掉 `"\n"`,然後我懶得用 `cin.ignore()`,所以用了一個取巧的方式,就是都用 `getline(cin, line)`,然後第一次就將 `n = stoi(line)`,強制把 line 的數字轉整數指定給 n。
第二次讀取空行,也沒差。
在 C++ 控制小數位數的話,則用到:
:::info
`fixed`、`setprecision()` 皆為 manipulator(操縱器),定義於 `<iomanip>` 標頭檔中,
`fixed` 指示 cout 用固定點表示法(fixed-point notation)顯示浮點數,表示會顯示 `123.4567`,而非科學記號的 `1.234567e+02`。
用 `fixed` 將小數點固定後,再由指定小數位數有幾位的 `setprecision()` 控制。
所以若僅顯示 4 位小數點,則寫成:
`cout << fixed << setprecision(4);`
:::
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
string line;
getline(cin, line);
n = stoi(line);
getline(cin, line);
for (int i = 0; i < n; ++i){
map <string, int> counts;
int total = 0;
while (getline(cin, line)){
if (line.empty()) break;
counts[line]++;
total++;
}
for (auto &p : counts){
cout << fixed << setprecision(4);
cout << p.first << " " << (p.second * 100.0 / total) << '\n';
}
if (i != n - 1){
cout << '\n';
}
}
return 0;
}
```
### Playlist
Source:https://cses.fi/problemset/task/1141
單字:
- successive (adj.):接連的,連續的。
註:作者第一次看到跟腦袋想得不一樣的單字XD,還以為是成功的意思。
該題用 map 結合雙指標滑動窗口的方式去解。
Why Sliding Window?
核心就在敘述之中!
> What is the longest sequence of successive songs where each song is unique?
以下是用範例測資來做滑動窗口的示意:
```
8
1 2 1 3 2 7 4 2
```
1. 12。
2. 121 -> 去掉最前面的 1,變成 21。
3. 2132 -> 去掉最前面的 2,變成 132。
4. 13274 -> 即為答案
那為了避免還出現在不重複的窗口中,所以索引要 + 1(`last_index[song] + 1`),如 121 去掉最前面的 1 變成 21,索引要在 2 上面。
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> songs(n);
for (int i = 0; i < n; ++i) {
cin >> songs[i];
}
map<int, int> last_index; // 紀錄每首歌最後出現的位置
int left = 0, max_len = 0;
for (int right = 0; right < n; ++right) {
int song = songs[right];
if (last_index.count(song)) {
// 若這首歌出現過,並且位置在目前 left 之後
left = max(left, last_index[song] + 1);
}
last_index[song] = right; // 更新這首歌的最新位置
max_len = max(max_len, right - left + 1); // 計算最大長度
}
cout << max_len;
return 0;
}
```
參考資料
---
[Multimap in C++ STL - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/multimap-associative-containers-the-c-standard-template-library-stl/)
[Map in C++ STL - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/map-associative-containers-the-c-standard-template-library-stl/)
[C++ 容器类 <map> | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-libs-map.html)