std::map适合小规模单词统计且需字典序输出,std::unordered_map更适合大规模无序场景;预处理需剥离标点并统一大小写,输出频次排序需手动拷贝至vector后sort。

用 std::map 统计单词频率最直接
不需要额外依赖,std::map<:string int></:string> 是 C++ 标准库中最自然的选择。它自动按字典序排序,插入和查找都是 O(log n),对几千个单词完全够用。
常见错误是把 std::string 拆成单个字符处理,或误用 operator[] 时没注意默认构造值(int 初始化为 0,这点其实安全)。
- 读入单词时建议用
std::cin >>或std::getline配合std::istringstream,避免空格/换行干扰 - 如果区分大小写,直接用原字符串;否则统一转小写,可用
std::tolower遍历每个字符 - 标点符号要提前剥离,比如用
std::isalnum过滤,或用std::regex_replace去掉非字母数字字符
std::unordered_map 更快但不保序
当单词量超过 10 万,且不需要按字母顺序输出结果时,std::unordered_map 是更优解——平均查找和插入是 O(1),实际性能提升明显。
容易踩的坑:自定义哈希或等价比较函数写错会导致崩溃或统计错误;另外,std::unordered_map 不提供 begin() / end() 的有序遍历,排序得额外拷贝到 std::vector 再 std::sort。
- 声明方式:
std::unordered_map<:string int></:string>即可,无需额外模板参数(std::hash<:string></:string>和std::equal_to是默认的) - 内存占用略高,但现代编译器优化后差距不大
- 如果输入含大量重复短字符串(如“the”、“a”),
std::unordered_map的缓存局部性更好
忽略大小写和标点的预处理怎么做
真实文本里 “Hello,”、“HELLO”、“hello!” 都该算同一个词,但 C++ 默认不会帮你归一化。
别用 std::transform + std::toupper 简单粗暴转换——它对非 ASCII 字符(如中文、带重音字母)行为未定义,而且会把标点也一起转,反而更乱。
- 推荐逐字符判断:
if (std::isalnum(c)) word += std::tolower(c); - 用
std::locale更严谨,但多数英文场景没必要;若需支持 locale-aware 转换,才考虑std::use_facet<:ctype>>(loc).tolower(c)</:ctype> - 正则方案(
std::regex_replace(s, std::regex(R"([^a-zA-Z0-9]+)"), " "))简洁但性能差,只适合预处理少量文本
输出时按频次降序排列
std::map 按键排序,std::unordered_map 按哈希桶顺序,都不按频次排。想看“出现最多的前 10 个词”,必须手动排序。
别在循环里反复调用 std::max_element——时间复杂度变成 O(n²),万级数据就卡住。
- 标准做法:把 map 内容拷贝进
std::vector<:pair int>></:pair>,再用std::sort(v.begin(), v.end(), [](auto& a, auto& b) { return a.second > b.second; }) - 如果只要 top-K,用
std::partial_sort或std::nth_element更高效 - 注意:频次相同时是否要按字典序二次排序,得显式写进 lambda 里,否则顺序不确定
真正麻烦的不是统计本身,而是文本清洗的边界情况——缩写(don’t)、连字符(state-of-the-art)、引号包裹的词("hello")怎么切分。这些得根据你的语料定规则,没有通用解法。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











