Setelah memahami implementasi manual hash table, sekarang kita memakai versi siap pakai dari STL: std::unordered_map<K, V> dan std::unordered_set<T>. Kedua container ini menggunakan separate chaining di bawah hood, memberi kompleksitas rata-rata O(1) untuk insert, find, dan erase. Pada Chapter ini kita akan mempelajari API dasar, bagaimana menyediakan hash function dan equality predicate untuk tipe custom, lalu membandingkannya dengan std::map (BST) untuk membantu kita memilih container yang tepat.
API Dasar #
Mekanisme: Container Asosiatif Berbasis Hash #
std::unordered_map<K, V> menyimpan pasangan key-value unik. std::unordered_set<T> menyimpan himpunan key tanpa value. Keduanya:
- Tidak menyimpan urutan (berbeda dari
std::map/std::set). - Mengizinkan iterasi dengan urutan tidak ditentukan.
- Kompleksitas rata-rata O(1) untuk operasi dasar, O(n) worst case (semua collision).
Kode: unordered_map Dasar #
#include <iostream>
#include <unordered_map>
#include <string>
int main() {
std::unordered_map<std::string, int> um;
// Insert dengan operator[]
um["andi"] = 90;
um["budi"] = 85;
um["citra"] = 78;
// Insert dengan emplace (lebih efisien untuk value baru)
um.emplace("dewi", 92);
// Update value existing
um["budi"] = 88;
// Akses dengan at() — throws jika key tidak ada
try {
std::cout << "nilai andi = " << um.at("andi") << '\n';
std::cout << "nilai zaki = " << um.at("zaki") << '\n';
} catch (const std::out_of_range& e) {
std::cout << "exception: " << e.what() << '\n';
}
// Akses dengan operator[] — INSERT default bila tidak ada
int nilai_eka = um["eka"]; // "eka" tidak ada, um["eka"]=0
std::cout << "nilai eka (default) = " << nilai_eka << '\n';
std::cout << "size setelah akses eka = " << um.size() << '\n';
// find dengan iterator
auto it = um.find("citra");
if (it != um.end()) {
std::cout << "find citra -> " << it->second << '\n';
}
// erase
um.erase("dewi");
std::cout << "size setelah erase dewi = " << um.size() << '\n';
// Iterasi (urutan tidak ditentukan)
std::cout << "\nIsi unordered_map:\n";
for (const auto& [key, val] : um) {
std::cout << " " << key << " -> " << val << '\n';
}
// Statistik internal
std::cout << "\nStatistik:\n";
std::cout << " bucket_count = " << um.bucket_count() << '\n';
std::cout << " load_factor = " << um.load_factor() << '\n';
std::cout << " max_load_factor = " << um.max_load_factor() << '\n';
return 0;
}Output yang Dihasilkan:
nilai andi = 90
nilai zaki = exception: unordered_map::at
nilai eka (default) = 0
size setelah akses eka = 5
find citra -> 78
size setelah erase dewi = 4
Isi unordered_map:
eka -> 0
citra -> 78
budi -> 88
andi -> 90
Statistik:
bucket_count = 13
load_factor = 0.307692
max_load_factor = 1- Baris
um["andi"] = 90;: operator[] melakukan insert bila key belum ada, atau update bila sudah ada. Kompleksitas rata-rata O(1). - Baris
um.emplace("dewi", 92);: emplace membangun pair langsung di tempat tanpa copy sementara — sedikit lebih efisien daripadaum["dewi"] = 92. - Baris
um.at("andi"): akses read-only dengan pengecekan key. Bila key tidak ada, lemparstd::out_of_range— berbeda dari operator[] yang insert default. - Baris
um["eka"]: akses key yang tidak ada — operator[] memasukkan pair{"eka", 0}(default-constructedint = 0). Efek samping: ukuran map bertambah. - Baris
um.find("citra"): kembalikan iterator ke entry, atauum.end()bila tidak ketemu. Tidak ada efek samping, tidak insert. - Baris
for (const auto& [key, val] : um): structured binding C++17 — unpack pair menjadi variabel terpisah. Urutan iterasi bergantung posisi internal bucket, tidak dijamin alfabetis. - Hasil
bucket_count = 13, load_factor = 0.307692: setelah insert 5 entry (andi, budi, citra, dewi, eka), implementasi libstdc++ memilih ukuran bucket prima berikutnya setelah 5 — yaitu 13. Setelaherase("dewi")size menjadi 4, sehingga α = 4/13 ≈ 0.308.max_load_factor = 1adalah default STL; rehash terjadi ketika α melampaui 1.
unordered_set #
std::unordered_set<T> adalah versi tanpa value — hanya menyimpan himpunan key. API mirip dengan unordered_map tetapi tanpa pasangan value.
Kode: Himpunan dengan unordered_set #
#include <iostream>
#include <unordered_set>
#include <vector>
int main() {
std::unordered_set<int> s;
s.insert(10);
s.insert(20);
s.insert(10); // duplikat, diabaikan
s.insert(30);
std::cout << "size = " << s.size() << '\n'; // 3
std::cout << "count(10) = " << s.count(10) << '\n'; // 1
std::cout << "count(99) = " << s.count(99) << '\n'; // 0
// Hapus duplikat dari vector
std::vector<int> v = {1, 2, 3, 2, 1, 4, 5, 3};
std::unordered_set<int> seen(v.begin(), v.end());
std::cout << "\nElemen unik dari {1,2,3,2,1,4,5,3}:\n";
for (int x : seen) std::cout << x << ' ';
std::cout << '\n';
// Cek irisan dua himpunan
std::unordered_set<int> a = {1, 2, 3, 4, 5};
std::unordered_set<int> b = {3, 4, 5, 6, 7};
std::cout << "\nIrisan a dan b:\n";
for (int x : a) {
if (b.count(x)) std::cout << x << ' ';
}
std::cout << '\n';
return 0;
}Output yang Dihasilkan:
size = 3
count(10) = 1
count(99) = 0
Elemen unik dari {1,2,3,2,1,4,5,3}:
5 4 3 2 1
Irisan a dan b:
5 4 3- Baris
s.insert(10); s.insert(10);: insert kedua tidak berdampak karenaunordered_sethanya menyimpan key unik.size()tetap 1. - Baris
s.count(10): kembalikan 0 atau 1 — count bukan total kemunculan (karena unik), tetapi idiomatik untuk “apakah ada?”. - Baris
std::unordered_set<int> seen(v.begin(), v.end());: konstruktor range — deduplikasi otomatis dari iterator. Efektif untuk membersihkan duplikat dari koleksi. - Baris
for (int x : seen): urutan iterasi tidak ditentukan — pada output di atas5 4 3 2 1, bukan urutan input1 2 3 4 5. Iterasi mengikuti urutan internal bucket. - Hasil irisan
5 4 3: iterasiadan cek keberadaan dib— O(n) rata-rata, jauh lebih cepat daripada nested loop O(n²). Urutan output juga tidak terurut.
Hash Function dan Equality untuk Tipe Custom #
STL menyediakan std::hash<T> untuk tipe bawaan (int, double, string, pointer). Untuk tipe custom, kita perlu menyediakan:
- Hash function — objek callable
size_t operator()(const T&) const. - Equality predicate — objek callable
bool operator()(const T&, const T&) const(defaultstd::equal_to<T>yang memakaioperator==).
Kode: unordered_map dengan Key Custom #
#include <iostream>
#include <unordered_map>
#include <string>
#include <functional>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
struct PointHash {
std::size_t operator()(const Point& p) const {
std::size_t h1 = std::hash<int>{}(p.x);
std::size_t h2 = std::hash<int>{}(p.y);
// hash_combine idiom: XOR dengan konstanta emas
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
}
};
int main() {
std::unordered_map<Point, std::string, PointHash> m;
m[{0, 0}] = "origin";
m[{1, 2}] = "kanan-atas";
m[{3, 4}] = "kanan-jauh";
m[{-1, -1}] = "kiri-bawah";
Point target{1, 2};
auto it = m.find(target);
if (it != m.end()) {
std::cout << "Point(1,2) -> " << it->second << '\n';
}
std::cout << "\nSemua entry:\n";
for (const auto& [pt, label] : m) {
std::cout << " (" << pt.x << "," << pt.y << ") -> " << label << '\n';
}
std::cout << "\nStatistik:\n";
std::cout << " bucket_count = " << m.bucket_count() << '\n';
std::cout << " load_factor = " << m.load_factor() << '\n';
return 0;
}Output yang Dihasilkan:
Point(1,2) -> kanan-atas
Semua entry:
(-1,-1) -> kiri-bawah
(3,4) -> kanan-jauh
(1,2) -> kanan-atas
(0,0) -> origin
Statistik:
bucket_count = 13
load_factor = 0.307692- Baris
bool operator==(const Point& o) const: wajib didefinisikan untuk equality. Defaultstd::equal_to<Point>akan memanggil operator ini. - Baris
struct PointHash { std::size_t operator()(const Point& p) const; }: objek function yang meng-hashPoint. Harus deterministic dan uniform. - Baris
std::size_t h1 = std::hash<int>{}(p.x);: hash setiap field denganstd::hash<int>. Inisialisasi{}membuat instance sementara. - Baris
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));: idiomhash_combinedari Boost — kombinasi XOR dengan konstanta emas0x9e3779b9(rasio fibonacci) plus rotasi bit. Menghasilkan distribusi seragam untuk pasangan field. - Baris
std::unordered_map<Point, std::string, PointHash> m;: tipe map dengan tiga template argumen: key, value, hash function type. Karenaoperator==sudah didefinisikan, equality predicate default bekerja. - Baris
m[{0, 0}] = "origin";: aggregate initialization membuatPoint{0,0}untuk key. C++17 mendukung syntax ini bilaPointadalah aggregate. - Hasil
bucket_count = 13, α = 0.308: 4 entry pada 13 bucket. Implementasi libstdc++ memilih ukuran prima terdekat di atas ambang load_factor, yaitu 13. Iterasi(-1,-1), (3,4), (1,2), (0,0)tidak mengikuti urutan insert maupun urutan numerik — itu mengikuti posisi internal bucket.
namespace std {
template<> struct hash<Point> {
std::size_t operator()(const Point& p) const noexcept {
std::size_t h1 = std::hash<int>{}(p.x);
std::size_t h2 = std::hash<int>{}(p.y);
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
}
};
}
// Sekarang std::unordered_map<Point, V> bekerja tanpa argumen hash eksplisit.
unordered_map vs std::map #
Tabel perbandingan membantu kita memilih:
| Aspek | std::unordered_map | std::map |
|---|---|---|
| Implementasi | Hash table + chaining | BST merah-hitam |
| Lookup rata-rata | O(1) | O(log n) |
| Lookup worst case | O(n) | O(log n) |
| Insert rata-rata | O(1) | O(log n) |
| Iterasi terurut | Tidak | Ya (in-order) |
| Lower/upper bound | Tidak ada | Ada |
| Memory overhead | Tinggi (bucket) | Sedang (pointer per node) |
| Sensitif terhadap hash lemah | Ya | Tidak |
Performa: Praktik versus Teori #
Worst case O(n) terjadi ketika semua key terpetakan ke bucket yang sama. STL std::unordered_map tidak melindungi dari ini secara default. Bila kita memakai std::unordered_map<int, V> dan key di-set ke 0, 7, 14, 21, ... (kelipatan bucket_count default), semua entry tumpuk di satu bucket.
Mitigasi #
- Pilih hash function yang seragam untuk distribusi key yang diketahui.
- Atur
max_load_factor(0.5)untuk memaksa rehash lebih awal — mengurangi panjang rantai. - Reserve upfront:
um.reserve(n)memanggil rehash agarbucket_countcukup untuknentry tanpa intermediate rehash. - Profile: bila lookup lambat, gunakan
bucket_count()danbucket_size(i)untuk inspeksi distribusi.
Ringkasan #
std::unordered_map<K, V>danstd::unordered_set<T>adalah hash container STL dengan kompleksitas rata-rata O(1).- API utama:
operator[],at(),find(),insert/emplace,erase(),count(),contains()(C++20). operator[]insert default bila key tidak ada;at()throws;find()tidak mengubah map.- Untuk tipe custom, sediakan hash function (callable
size_t operator()(T)) dan equality predicate (defaultoperator==). - Idiom
hash_combinedengan konstanta emas0x9e3779b9menjadi standar untuk menggabung hash beberapa field. - Pilih
unordered_mapuntuk lookup cepat tanpa urutan;mapuntuk iterasi terurut, range query, atau ketika hash function lemah.