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.

key không trùng tự sắp xếp theo key O(log n) phù hợp đếm tần suất

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"].

Trong tài liệu gốc, map được giới thiệu như một cấu trúc giúp thao tác với các chỉ số không thuận tiện cho mảng, với độ phức tạp thường là O(log n).

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ĩaVí dụ
keyKhóa dùng để tìm kiếm. Mỗi key chỉ xuất hiện một lần."An", "Bai1"
valueGiá trị gắn với key.điểm số, số lần xuất hiện, trạng thái
firstTrường chứa key khi duyệt map.it->first
secondTrường chứa value khi duyệt map.it->second
Map mặc định sắp xếp các phần tử tăng dần theo key.

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
Chú ý: insert không thay đổi value nếu key đã tồn tại, còn mp[key] = value sẽ ghi đè value cũ.

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

Câu 1

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;
}
Câu 2

SCORES.CPP - Quản lý điểm

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;
}
Câu 3

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ỗiCá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 firstsecond.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.
Kết luận: Map rất mạnh cho các bài toán đếm, quản lý thông tin theo tên, xử lý truy vấn và ánh xạ dữ liệu.
style> body { user-select: none; }