不能直接用std::set逐行插入是因为其红黑树实现导致每次插入为o(log n)比较,字符串比较本身为o(l),合起来接近o(n log n × l),且不保留首次出现顺序;正确做法是用std::unordered_set去重+std::vector记录顺序,配合std::move避免深拷贝,实现零冗余保序去重。

为什么不能直接用 std::set<:string></:string> 逐行插入?
看似最直觉的做法——读一行、插进 std::set、再输出,实际在大量重复行(比如日志中成千上万个相同错误)时会显著变慢。因为 std::set 是红黑树,每次插入都要做 O(log n) 比较,而字符串比较本身是 O(L)(L 是行长度),合起来接近 O(n log n × L),且内存分配频繁。
更关键的是:它不保留首次出现顺序,而“按行去重”隐含要求输出顺序 = 第一次出现的顺序。
- 用
std::unordered_set可提速插入/查找到平均 O(1),但依然不保序 - 若需保序,必须额外维护一个
std::vector或类似结构记录唯一行的出现顺序 - 真正快的做法是:用
std::unordered_set去重 +std::vector记录顺序,两者配合,避免重复字符串拷贝
怎么用 std::unordered_set + std::vector 实现零冗余保序去重?
核心思路是只存一次字符串内容:用 std::unordered_set 判断是否见过,用 std::vector 存指针或引用(但 C++ 中 vector 不能存裸引用),所以实际存 std::string 的移动值,靠 move 语义避免深拷贝。
std::vector<:string> unique_lines;
std::unordered_set<:string> seen;
std::string line;
while (std::getline(std::cin, line)) {
if (seen.insert(line).second) { // insert 返回 pair<iterator bool>,.second 为 true 表示新插入
unique_lines.push_back(std::move(line)); // 移动构造,原 line 变为空但合法
}
}
</iterator></:string></:string>
-
seen.insert(line)自动处理查找+插入,比先find再insert少一次哈希计算 -
std::move(line)让push_back调用移动构造,避免复制整个字符串缓冲区 - 如果输入来自文件,用
std::ifstream替换std::cin,并确保打开时设.sync_with_stdio(false)加速
遇到超长行或空行要怎么处理?
std::getline 默认以 '\n' 为界,遇到空行会读出长度为 0 的 std::string,这本身是合法行——是否去重取决于需求。常见误区是把空行当“无效”跳过,结果丢失原始格式语义。
- 若业务允许空行去重(即多个连续空行只留一个),当前逻辑已支持——空字符串也会被
seen正确识别 - 若需保留所有空行(即空行不参与去重),加判断:
if (line.empty()) { unique_lines.push_back(std::move(line)); continue; } - 超长行(如 >1MB)不会导致崩溃,但
std::getline会一次性分配足够内存;若内存敏感,可改用循环读字符 + 手动缓冲,但绝大多数场景没必要
性能瓶颈真正在 I/O,而不是哈希表?
实测表明:当输入规模达百万行以上时,90% 时间花在 std::getline 和 std::cout 上,而非 unordered_set 操作。一个简单但有效的提速手段是关闭同步:
std::ios_base::sync_with_stdio(false); std::cin.tie(nullptr);
- 关同步后,C++ iostream 不再与 C stdio(如
printf)保持一致,但大幅提升读取速度 -
cin.tie(nullptr)解除cin与cout的绑定,避免每次输入前 flush 输出缓冲区 - 注意:关同步后,不能再混用
scanf/printf和cin/cout,否则行为未定义
真正需要优化去重逻辑的场景极少——除非每行都极短(如单字符)且重复率极高,此时可考虑用 std::unordered_set 的定制哈希器或小字符串优化(SSO)对齐,但普通业务几乎用不到。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











