用std::map统计单词频率最直接,键为单词、值为频次;需清洗标点、转小写;若无需排序可用std::unordered_map;按频次输出需转vector后sort。

用 std::map<:string int></:string> 统计单词频率最直接
这是 C++ 里最常用、语义最清晰的做法:把单词当键,出现次数当值。每次读到一个单词,就对对应键的值加 1。
注意点:
-
std::map自动按字典序排序键,如果不需要排序,改用std::unordered_map(平均 O(1) 插入/查找) - 读取单词时别直接用
std::cin >> str就完事——它跳过所有空白,但不会处理标点。比如"hello!"和"hello"会被视为不同单词 - 建议先清洗字符串:移除末尾标点(如
'.',',','!'),转小写(避免"Hello"和"hello"被算作两个词)
如何安全地从输入流提取并清洗单词
别依赖 operator>> 原样吞入,容易把标点粘在单词上。推荐边读边清理:
std::string word;
while (std::cin >> word) {
// 移除末尾标点(只处理结尾,不碰中间的撇号如 "don't")
while (!word.empty() && !std::isalnum(word.back())) {
word.pop_back();
}
// 转小写
std::transform(word.begin(), word.end(), word.begin(), ::tolower);
if (!word.empty()) {
word_count[word]++;
}
}
常见坑:
立即学习“C++免费学习笔记(深入)”;
- 没判空就调
word.back()→ 崩溃 - 用
std::toupper/std::tolower但没传static_cast<unsigned char>→ 在某些 locale 下对负 char 出未定义行为(实际中用::tolower更稳妥) - 清洗太激进:比如去掉所有非字母字符,会把
"C++"变成"c",丢失原意
std::map vs std::unordered_map 性能与行为差异
两者接口几乎一样,但底层和行为关键不同:
-
std::map:红黑树实现,键有序,insert/find是 O(log n),遍历时按键升序输出 -
std::unordered_map:哈希表实现,平均 O(1),但最坏 O(n);不保证顺序;需要为自定义类型提供哈希函数(std::string已内置) -
内存占用:
unordered_map通常更大(哈希桶开销),且可能触发 rehash 导致短暂卡顿 - 如果后续要按频次排序输出(而非按键),两者都得额外拷贝到
vector再sort,这时底层容器选择影响不大
输出前按频次降序排列单词
map 和 unordered_map 都不支持按 value 排序,必须中转:
std::vector<std::pair<std::string, int>> vec(word_count.begin(), word_count.end());
std::sort(vec.begin(), vec.end(), [](const auto& a, const auto& b) {
return a.second > b.second; // 频次高在前
});
注意:
- 别写
a.second >= b.second——sort要求严格弱序,相等时返回 false,否则行为未定义 - 如果频次相同还想按字典序排单词,改成:
a.second != b.second ? a.second > b.second : a.first < b.first - 频繁查最大频次?考虑用
std::priority_queue替代,但插入 O(log n),且不能删任意元素











