BÀI GIẢNG MAP TRONG C++
Map là cấu trúc dữ liệu dùng để lưu các cặp khóa - giá trị. Rất hữu ích khi chỉ số không phải số nhỏ hoặc không thể dùng mảng trực tiếp.
1. Đặt vấn đề
Trong nhiều bài toán, ta cần lưu thông tin theo một chỉ số đặc biệt. Chỉ số đó có thể là một xâu, một mã học sinh, tên bài, tên người dùng hoặc một số rất lớn. Khi đó, dùng mảng thông thường sẽ không phù hợp.
Dùng mảng
Phù hợp khi chỉ số là số nguyên nhỏ, liên tục, ví dụ a[1], a[2], ...
Dùng map
Phù hợp khi chỉ số là xâu hoặc số lớn, ví dụ mp["An"], mp["BAI1"].
2. Khái niệm map
map là cấu trúc dữ liệu lưu các cặp key → value.
| Thành phần | Ý nghĩa | Ví dụ |
|---|---|---|
| key | Khóa dùng để tìm kiếm. Mỗi key chỉ xuất hiện một lần. | "An", "Bai1" |
| value | Giá trị gắn với key. | điểm số, số lần xuất hiện, trạng thái |
| first | Trường chứa key khi duyệt map. | it->first |
| second | Trường chứa value khi duyệt map. | it->second |
3. Khai báo map
3.1. Khai báo cơ bản
#include <bits/stdc++.h>
using namespace std;
int main() {
map<string, int> dem; // key: string, value: int
map<int, string> ten; // key: int, value: string
map<int, int, greater<int>> giam; // sắp xếp key giảm dần
return 0;
}
3.2. Map sắp xếp giảm dần theo key
map<int, string, greater<int>> mp;
mp[3] = "ba";
mp[1] = "mot";
mp[5] = "nam";
// Khi duyệt, key sẽ theo thứ tự: 5, 3, 1
3.3. Map dùng hàm so sánh tự viết
Khi muốn tự quy định thứ tự sắp xếp key, ta có thể tạo một struct so sánh.
struct cmp {
bool operator()(const string &a, const string &b) const {
if (a.size() != b.size()) return a.size() < b.size();
return a < b;
}
};
int main() {
map<string, int, cmp> mp;
mp["abc"] = 1;
mp["a"] = 2;
mp["ab"] = 3;
for (auto x : mp) {
cout << x.first << " " << x.second << '\n';
}
}
4. Các thao tác với map
4.1. Thêm và cập nhật phần tử
map<string, int> mp;
// Cách 1: insert
mp.insert(make_pair("An", 10));
mp.insert({"Binh", 8});
// Nếu key đã tồn tại, insert KHÔNG thay đổi value cũ
mp.insert({"An", 20}); // mp["An"] vẫn là 10
// Cách 2: dùng toán tử []
mp["Cuong"] = 9;
mp["An"] = 20; // nếu key đã có, value bị thay đổi thành 20
mp["An"] += 5; // cộng thêm 5 điểm cho An
4.2. Duyệt map
map<string, int> mp;
mp["An"] = 20;
mp["Binh"] = 8;
mp["Cuong"] = 9;
// Duyệt bằng auto
for (auto x : mp) {
cout << x.first << " " << x.second << '\n';
}
// Duyệt bằng iterator
for (map<string, int>::iterator it = mp.begin(); it != mp.end(); ++it) {
cout << it->first << " " << it->second << '\n';
}
4.3. Tìm kiếm key
map<string, int> mp;
mp["An"] = 20;
string x = "Nam";
if (mp.find(x) != mp.end()) {
cout << mp[x];
} else {
cout << 0;
}
// Chú ý: mp[x] khi x chưa có sẽ tự tạo key x với value mặc định là 0.
5. Ví dụ code thường gặp
Đếm tần suất số
int n;
cin >> n;
map<int, int> cnt;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
cnt[x]++;
}
for (auto p : cnt) {
cout << p.first << " xuat hien " << p.second << " lan\n";
}
Đếm tần suất từ
int n;
cin >> n;
map<string, int> cnt;
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
cnt[s]++;
}
cout << cnt["cpp"];
6. Bảng hàm thường dùng
| Hàm / cú pháp | Ý nghĩa | Độ phức tạp |
|---|---|---|
| mp[key] | Truy cập value của key. Nếu key chưa có, map tự tạo key mới với value mặc định. | O(log n) |
| mp.insert({'key', value}) | Thêm phần tử nếu key chưa tồn tại. | O(log n) |
| mp.find(key) | Tìm key, trả về iterator. | O(log n) |
| mp.count(key) | Kiểm tra key có tồn tại không. Với map, kết quả là 0 hoặc 1. | O(log n) |
| mp.erase(key) | Xóa phần tử có key. | O(log n) |
| mp.size() | Số lượng key đang có trong map. | O(1) |
| mp.empty() | Kiểm tra map có rỗng không. | O(1) |
| mp.clear() | Xóa toàn bộ phần tử. | O(n) |
| mp.begin(), mp.end() | Dùng để duyệt map từ đầu đến cuối. | O(1) |
| mp.lower_bound(key) | Phần tử đầu tiên có key ≥ giá trị cần tìm. | O(log n) |
| mp.upper_bound(key) | Phần tử đầu tiên có key > giá trị cần tìm. | O(log n) |
7. Bài tập MAP
DICT.CPP - Từ điển
Nam có một quyển từ điển. Có N thao tác, mỗi thao tác thuộc một trong hai loại:
- 1 X: học từ X.
- 2 Y: hỏi từ Y đã được học bao nhiêu lần.
Với mỗi truy vấn loại 2, in ra số lần từ đó đã được học.
DICT.INP
6 1 abc 1 a 2 b 2 a 1 a 2 a
DICT.OUT
0 1 2
Ý tưởng
Dùng map<string,int> cnt. Khi học từ X thì cnt[X]++. Khi hỏi Y thì in cnt[Y].
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
map<string, int> cnt;
while (n--) {
int type;
string x;
cin >> type >> x;
if (type == 1) {
cnt[x]++;
} else {
cout << cnt[x] << '\n';
}
}
return 0;
}
SCORES.CPP - Quản lý điểm
Có Q truy vấn với điểm của học sinh:
- 1 X Y: cộng thêm Y điểm cho học sinh tên X.
- 2 X: xóa điểm của học sinh X.
- 3 X: in điểm của học sinh X, nếu chưa có thì in 0.
SCORES.INP
7 1 Jesse 20 1 Jess 12 1 Jess 18 3 Jess 3 Jesse 2 Jess 3 Jess
SCORES.OUT
30 20 0
Ý tưởng
Dùng map<string,long long> score. Truy vấn loại 1 thì cộng điểm, loại 2 thì xóa key, loại 3 thì tìm và in kết quả.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin >> q;
map<string, long long> score;
while (q--) {
int type;
string name;
cin >> type >> name;
if (type == 1) {
long long y;
cin >> y;
score[name] += y;
} else if (type == 2) {
score.erase(name);
} else {
auto it = score.find(name);
if (it == score.end()) cout << 0 << '\n';
else cout << it->second << '\n';
}
}
return 0;
}
CONTEST.CPP - Tính điểm cuộc thi
Có C thí sinh, P bài toán và S lần nộp bài. Mỗi bài có điểm riêng. Mỗi lần nộp gồm tên thí sinh, tên bài và kết quả AC hoặc WA.
Cần in ra tên và điểm của các thí sinh tham gia cuộc thi theo đúng thứ tự ban đầu.
Ý tưởng dùng map
- Dùng map để đổi tên thí sinh thành chỉ số.
- Dùng map để lưu điểm của từng bài.
- Dùng set để đánh dấu cặp (thí sinh, bài) đã AC, tránh cộng điểm lặp.
- Nếu tên thí sinh không thuộc danh sách ban đầu thì bỏ qua lần nộp đó.
map<string, int> idStudent; // tên thí sinh -> chỉ số
map<string, long long> point; // tên bài -> điểm
vector<long long> total(C + 1, 0);
set<pair<int, string>> accepted; // (id thí sinh, tên bài đã AC)
// Với mỗi lần nộp: name, problem, result
// Nếu name không thuộc danh sách thí sinh thì bỏ qua.
// Nếu result == "AC" và cặp (id, problem) chưa từng AC:
// total[id] += point[problem];
// accepted.insert({id, problem});
8. Lỗi hay gặp khi dùng map
| Lỗi | Cách sửa |
|---|---|
| Dùng mp[x] chỉ để kiểm tra x có tồn tại hay không. | Nên dùng find hoặc count, vì mp[x] có thể tạo key mới. |
| Nhầm first và second. | first là key, second là value. |
| Nghĩ map giữ thứ tự nhập vào. | Map sắp xếp theo key, không giữ thứ tự nhập. Nếu cần giữ thứ tự nhập, dùng thêm vector. |
| Dùng map trong bài cần tốc độ cực nhanh nhưng không cần thứ tự key. | Có thể cân nhắc unordered_map, nhưng cần hiểu rủi ro va chạm và thứ tự không xác định. |