C++merge合并两个有序序列
C++里merge不是“拼凑”,是让两个有序队列自然汇流
上周帮实习生调一段排序合并的代码,他写了三层for循环手动比大小、插元素,跑得慢还容易越界。我顺手换成std::merge,性能翻倍,代码从42行缩到5行。他盯着控制台输出愣了几秒:“原来merge真不是把两段数组头尾接一起?”
这恰恰是很多人对std::merge最实在的误解——它不负责排序,只负责“归并”;它不关心你给的序列怎么来的,只认一个铁律:左右都必须已升序(或同向自定义序)。
如果你正被LeetCode第88题卡住,或在写归并排序的merge环节反复崩溃,又或者刚发现std::inplace_merge编译不过……别急着翻文档,先理清三件事:merge要什么输入、产出什么、以及最容易踩的三个“静默陷阱”。
std::merge定义在<algorithm>里,最常用签名是:
std::merge(first1, last1, first2, last2, d_first, comp?);
四个迭代器划出两段各自有序的区间,第五个是目标起始位置。注意:它不修改原序列,而是把结果“倒进”你指定的另一块内存里。就像把两杯分层的果汁(橙汁在上、苹果汁在下)缓缓注入第三个空杯,过程中始终按颜色深浅交替倾倒,最终得到一杯均匀渐变的混合果汁——前提是,你这两杯本来就是各自按浓度排好序的。
有人试过直接对vector a和b merge到a末尾,结果一片乱码。问题不在函数,而在目标空间没预留足够容量。std::merge不会帮你push_back,它只做“覆写式搬运”。所以实际用时得提前reserve,或用back_inserter包装器:
vector<int> res;
res.reserve(a.size() + b.size()); // 避免多次realloc
merge(a.begin(), a.end(), b.begin(), b.end(), back_inserter(res));
这里back_inserter是关键桥梁——它把每次赋值操作转成push_back,让merge“以为”自己在写普通迭代器,实则悄悄扩容。没它,你就得自己算好位置、确保空间够,否则轻则数据覆盖,重则UB(未定义行为)。
第二个坑藏在比较逻辑里。默认用<,但如果你的序列是降序呢?或者存的是自定义结构体?这时必须传入comp谓词,且务必保证两段序列用的是同一套比较规则。曾见同事把升序vector A和降序vector B硬塞给默认merge,结果前半截对、后半截崩——因为merge内部假设“当前A最小值 ≤ 当前B最小值”才取A,但降序B的“最小值”其实是它最后一个元素。
更隐蔽的是第三个陷阱:迭代器有效性。merge要求所有迭代器在调用期间有效。这意味着:不能拿vector的begin()去merge,同时又在中途clear()或resize()它;也不能把string的c_str()当char指针传进去——c_str()返回的指针可能随string修改失效。实操中,宁可拷贝一份data(),也别赌生命周期。
其实std::merge最被低估的价值,是它和std::inplace_merge的配合。后者真正在原地归并(比如把[1,3,5,2,4,6]中前3个和后3个归并成[1,2,3,4,5,6]),但它要求待归并的两段在内存中连续且相邻。很多教程只讲理论,却不说清楚:inplace_merge的性能优势只有在“已有部分有序数据需局部修正”时才凸显,比如日志系统按时间追加新批次后,只需归并最后两个块,而非全量重排。
回到开头那个实习生的问题:为什么他的手动合并总出错?因为他把“比较→选小→移动指针”拆成了孤立步骤,漏掉了边界判断的耦合性——而std::merge把这套逻辑固化在标准实现里,经受过数十年编译器和STL版本的锤炼。它不炫技,但足够稳;不省事,但省心。
下次再遇到两个有序序列要合成一个,别本能地打开编辑器写while循环。先问自己:输入真有序吗?目标空间够大吗?比较规则一致吗?这三个问题答得踏实,std::merge就会像老伙计一样,默默把活干利索。
它不解决排序问题,但让归并这件事,从此有了确定解。


还没有评论,来说两句吧...