1. 项目概述
在数据处理和文件操作中,经常需要将两个已经有序的文本文件合并成一个新的有序文件。很多初学者会犯一个典型错误:将两个文件全部读入内存,然后重新排序。这不仅浪费内存资源,还忽略了原始文件已经有序这一重要前提条件。
正确的做法是采用归并算法(Merge Algorithm),这是处理有序数据合并的标准方法。归并算法的核心思想是同时遍历两个输入文件,每次比较当前行,将较小的行写入输出文件,然后移动相应文件的指针。这种方法的时间复杂度是O(n),空间复杂度仅为O(1),是最高效的解决方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心原理与算法选择
2.1 为什么选择归并而非排序
当面对两个已经有序的文件时,重新排序是最糟糕的选择。原因有三:
- 破坏已有结构:原始文件已经有序,重新排序会忽略这一重要信息
- 内存消耗大:需要将两个文件全部加载到内存中
- 效率低下:排序算法的时间复杂度通常是O(n log n),而归并只需要O(n)
归并算法模拟了归并排序中的合并步骤,但省去了分割和递归的过程,直接利用两个输入序列的有序性进行合并。
2.2 归并算法的工作流程
归并算法的基本步骤如下:
- 打开两个输入文件和一个输出文件
- 从每个文件中读取一行,作为当前行
- 比较两个当前行:
- 将较小的行写入输出文件
- 从相应文件中读取下一行
- 重复步骤3,直到其中一个文件耗尽
- 将另一个文件的剩余行全部写入输出文件
这个流程保证了输出文件的有序性,同时只需要在内存中保存两行数据。
3. 实现细节与关键技巧
3.1 文件读取的正确方式
在C++中,文件读取有几个常见陷阱需要避免:
cpp复制// 错误示例:使用eof()判断文件结束
while (!file.eof()) {
std::getline(file, line);
// 处理line
}
// 正确做法:将getline直接放入循环条件
std::string line;
while (std::getline(file, line)) {
// 处理line
}
eof()只在读取失败后才会被设置,这意味着在读取最后一行后,eof()仍然是false,循环会多执行一次,导致重复处理最后一行。
3.2 行比较的注意事项
字符串比较默认使用字典序,这对于纯文本是合适的。但如果文件中存储的是数字,可能需要特殊处理:
cpp复制// 字符串比较(字典序)
if (line1 < line2) {
// line1较小
}
// 数值比较
int num1 = std::stoi(line1);
int num2 = std::stoi(line2);
if (num1 < num2) {
// line1表示的数值较小
}
对于更复杂的比较需求,可以使用自定义比较器:
cpp复制bool customCompare(const std::string& a, const std::string& b) {
// 自定义比较逻辑
return a.length() < b.length(); // 例如按长度比较
}
3.3 换行符处理
Windows和Unix-like系统使用不同的换行符(CRLF vs LF),这可能导致比较问题:
cpp复制// 移除行尾的换行符和空白字符
line.erase(line.find_last_not_of(" \n\r\t") + 1);
或者在读取时指定换行符处理方式(在C++17及以上版本):
cpp复制std::ifstream file("input.txt", std::ios::binary);
std::string line;
while (std::getline(file, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back(); // 移除CR
}
// 处理line
}
4. 完整实现代码
下面是一个完整的C
