std::unordered_map是最实用的词频统计容器,但必须配合清洗输入(如统一小写、处理标点、保留内部撇号)才能保证结果可信;直接使用>>读取或忽略大小写/标点会导致统计失效。

直接用 std::unordered_map<:string int></:string> 是最实用的选择,但光换容器不清洗输入,统计结果基本不可信。
为什么 std::unordered_map 比 std::map 更适合词频统计
两者接口一致,但底层差异直接影响性能和行为:
-
std::unordered_map基于哈希表,平均插入/查找是O(1);std::map是红黑树,稳定O(log n)—— 单词量超 1 万时差距明显 -
std::unordered_map不保证遍历顺序,如果你后续要按频次排序,这点反而无关紧要(反正得导出再排) - 内存占用略高,且可能触发 rehash;但对普通文本(几 MB 到百 MB),影响可忽略
-
std::string已自带哈希特化,无需额外写std::hash模板特化
标点、大小写、连字符:不处理就等于白统计
常见错误现象:"Hello" 和 "hello" 算两个词;"file." 和 "file" 被当不同键;"don't" 被切碎或吞掉撇号。
- 别用
std::istringstream >>直接读 —— 它只按空白分割,不管标点粘连 - 推荐手动扫描:用
std::isalpha()判断字母起始,累积连续字母序列 - 对每个提取出的单词,逐字符调用
std::tolower()转小写(不是只转首字母) - 保留内部撇号(如
"don't"),但清理首尾非字母数字字符("'hello!'" → "hello")
怎么安全地把单词塞进 unordered_map
两种写法等价,但下标法更简洁、不易漏初值:
-
freq[word]++:自动初始化为 0 再加 1,语义清晰 -
freq.insert({word, 1})返回pair<iterator bool></iterator>,需手动判断是否已存在再 ++ —— 多余且易错 - 避免在循环里反复构造临时
std::string;若从文件读,用std::ifstream配合get()或read()批量处理更稳
输出 TOP N 单词时,排序逻辑必须手写
unordered_map 本身不支持按 value 排序,硬绕只会让代码更脆弱:
- 必须先拷贝到
std::vector<:pair int>></:pair>,再用std::sort - 比较 lambda 要严格弱序:
a.second != b.second ? a.second > b.second : a.first (频次降序,同频时字典升序) - 别写
a.second >= b.second——std::sort要求相等时返回false,否则未定义行为 - 如果只要 TOP 10 且文本极大,可用
std::priority_queue,但得自定义比较器,且相同频次时必须补上字典序逻辑,否则会丢精度
真正卡住人的从来不是容器选型,而是清洗边界(比如缩写 "U.S.A."、带连字符的 "state-of-the-art"、引号包裹的 "'yes'")和排序稳定性。这些地方没测试用例压着,跑一次就错一次。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











