STL unordered_map 常见写法与深度解析

6 阅读3分钟

概述

unordered_map是 C++ STL 中的关联容器,基于哈希表(Hash Table) 实现。它提供键值对(Key-Value)的存储,支持平均 O(1) 时间复杂度的查找、插入和删除操作,是算法刷题和高效开发的重要工具。

核心 API

方法功能描述平均时间复杂度最坏时间复杂度
operator[]访问或插入元素O(1)O(n)
insert()插入元素O(1)O(n)
erase()删除元素O(1)O(n)
find()查找元素(返回迭代器)O(1)O(n)
count()统计键出现次数(0或1)O(1)O(n)
size()获取元素个数O(1)O(1)
empty()检查是否为空O(1)O(1)
clear()清空容器O(n)O(n)

1. 基础语法与定义

基本定义

#include <unordered_map>
#include <string>
using namespace std;

// 定义一个键为string,值为int的unordered_map
unordered_map<string, int> umap;

注意事项

  • 键必须支持哈希函数相等比较
  • operator[]会自动插入不存在的键(值为默认构造)
  • 迭代器顺序是不确定的,与插入顺序无关
  • 哈希冲突时性能会下降,最坏情况退化为O(n)

2. 常见写法与处理手段

基础操作:插入与访问

unordered_map<string, int> scores;

// 插入操作
scores.insert({"Alice", 95});        // 方法1
scores.emplace("Bob", 88);           // 方法2(更高效)
scores["Charlie"] = 92;              // 方法3

// 访问操作
cout << scores["Alice"];             // 输出95

// 访问不存在的键(会自动插入默认值)
cout << scores["David"];             // 输出0,并插入{"David", 0}

安全访问(避免自动插入)

unordered_map<string, int> scores;

// 错误写法:会插入默认值
// if(scores["Unknown"] > 0) { ... }

// 正确写法:使用find
auto it = scores.find("Unknown");
if(it != scores.end()) {
    cout << it->second;
} else {
    cout << "Key not found";
}

// 或者使用count
if(scores.count("Unknown") > 0) {
    cout << scores["Unknown"];
}

遍历unordered_map

unordered_map<string, int> scores = {{"Alice", 95}, {"Bob", 88}};

// 方法1:使用迭代器
for(auto it = scores.begin(); it != scores.end(); ++it) {
    cout << it->first << ": " << it->second << endl;
}

// 方法2:使用范围for循环(推荐)
for(const auto& pair : scores) {
    cout << pair.first << ": " << pair.second << endl;
}


3. 常用模式与技巧

模式1:频率统计

vector<int> nums = {1, 2, 3, 2, 1, 3, 3};
unordered_map<int, int> freq;

// 统计频率
for(int num : nums) {
    freq[num]++;
}

// 查找最大频率
int max_freq = 0;
for(const auto& [num, count] : freq) {
    max_freq = max(max_freq, count);
}

模式2:两数之和/查找互补

vector<int> nums = {2, 7, 11, 15};
int target = 9;
unordered_map<int, int> seen;  // 存储值和索引

for(int i = 0; i < nums.size(); i++) {
    int complement = target - nums[i];
    if(seen.find(complement) != seen.end()) {
        cout << "Found: " << seen[complement] << " and " << i;
        break;
    }
    seen[nums[i]] = i;
}

模式3:分组操作

vector<string> words = {"apple", "banana", "cat", "dog", "elephant"};
unordered_map<int, vector<string>> groups;  // 按长度分组

for(const string& word : words) {
    groups[word.length()].push_back(word);
}

// 输出所有长度为3的单词
for(const string& word : groups[3]) {
    cout << word << " ";
}

5. 进阶技巧与注意事项

技巧1:避免重复计算哈希

// 在循环中缓存哈希值
unordered_map<string, int> cache;
string key = "some_long_key";

// 避免多次计算同一个键的哈希
auto it = cache.find(key);
if(it == cache.end()) {
    // 只计算一次哈希
    cache[key] = compute_expensive_value();
}

技巧3:批量操作

unordered_map<string, int> map1, map2;

// 合并两个map
map1.insert(map2.begin(), map2.end());

// 删除满足条件的元素
for(auto it = map1.begin(); it != map1.end(); ) {
    if(it->second < 0) {
        it = map1.erase(it);
    } else {
        ++it;
    }
}

6. 常见错误与调试

错误1:误用operator[]

unordered_map<string, int> scores;

// 错误:会插入默认值0
if(scores["unknown"] == 0) {
    // 这里unknown键已经被插入了
}

// 正确:使用find或count
if(scores.find("unknown") != scores.end() && scores["unknown"] == 0) {
    // ...
}

错误2:迭代器失效

unordered_map<string, int> scores = {{"a", 1}, {"b", 2}};

// 错误:在删除后继续使用迭代器
for(auto it = scores.begin(); it != scores.end(); ++it) {
    if(it->first == "a") {
        scores.erase(it);  // 迭代器失效
        // ++it;  // 错误!
    }
}

// 正确:使用erase返回值
for(auto it = scores.begin(); it != scores.end(); ) {
    if(it->first == "a") {
        it = scores.erase(it);
    } else {
        ++it;
    }
}

错误3:误解性能特征

// 错误:认为unordered_map总是O(1)
// 实际上哈希冲突时可能退化为O(n)

// 正确:了解数据特征,必要时调整哈希函数
// 或者使用map(保证O(log n)最坏情况)