真正能逼近线性时间的多 set 合并方案是堆驱动的 k 路归并,时间复杂度 O(N log k)、空间复杂度 O(k),通过最小堆管理各 set 当前首元素,逐个弹出最小值并推进对应迭代器,避免重复扫描与低效插入。

不能用 std::set_union 一次性合并多个 std::set,也不能靠反复 insert 实现 O(N) —— 真正能逼近线性时间的方案只有一种:把所有 std::set 的迭代器“摊平”后做 k 路归并,但 std::set 迭代器不支持随机访问,必须用堆驱动。
为什么 std::set_union 不支持多个输入
std::set_union 是双路归并算法,签名固定为 set_union(InputIt1, InputIt1, InputIt2, InputIt2, OutputIt)。它不接受第三个范围,强行链式调用(比如先合 a+b 得 out,再 set_union(out.begin(), out.end(), c.begin(), c.end(), ...))会导致每次重扫整个中间结果,退化成 O(N×k)。更糟的是,std::set 的 begin() 是 const 迭代器,你没法用它作为输出目标,必须中转到 std::vector 或带插入能力的容器。
std::set::merge() 只能移动,不能复制合并
C++17 引入的 std::set::merge 成员函数作用是「把另一个 std::set 中所有可迁移的节点,直接拼接到当前 set」,但它要求两个 set 使用**完全相同的比较器类型**,且被 merge 的源 set 会清空。关键限制在于:set1.merge(set2); set1.merge(set3); 看似可行,但实际是两轮独立操作:第一轮后 set2 为空;第二轮 set3 仍需满足与 set1 同构(比如都用 std::less<int></int>),否则编译失败。它不解决多集合归并逻辑,只是高效转移节点。
真正高效的多 set 合并:堆驱动的 k 路归并
这是唯一能稳定达到 O(N log k) 时间、O(k) 额外空间的通用解法,适用于任意数量的已排序 std::set。核心是把每个 set 的首元素压入最小堆,每次弹出最小值,然后推进对应 set 的迭代器。
立即学习“C++免费学习笔记(深入)”;
- 定义元组
std::tuple<const std::set>::iterator, const std::set<int>::iterator, size_t></int></const>,存当前有效迭代器、尾迭代器、来源索引 - 用
std::priority_queue搭建小顶堆,自定义比较器:按*it值升序 - 对每个非空
std::set,取s.begin()和s.end()构造元组入堆 - 循环 pop 堆顶,将
*it写入结果std::vector或std::deque;若++it != end,则新元组重新入堆 - 注意:不能用
std::set作输出容器——它不支持 O(1) 尾插,且插入过程本身是 O(log M),会把整体拉回 O(N log N)
示例关键片段:
using Iter = std::set<int>::const_iterator;
struct Node {
Iter it, end;
size_t idx;
bool operator<(const Node& rhs) const { return *it > *rhs.it; }
};
std::priority_queue<Node> pq;
for (size_t i = 0; i < sets.size(); ++i) {
if (!sets[i].empty()) {
pq.push({sets[i].begin(), sets[i].end(), i});
}
}
std::vector<int> result;
result.reserve(total_size);
while (!pq.empty()) {
auto node = pq.top(); pq.pop();
result.push_back(*node.it);
if (++node.it != node.end) pq.push(node);
}
最容易被忽略的陷阱:比较器一致性与空集处理
所有参与合并的 std::set 必须使用相同实例化的比较器类型,例如不能混用 std::less<int></int> 和 std::greater<int></int>,哪怕语义等价也不行——模板参数不同即类型不同,编译不过。另外,任何 std::set 为空时,其 begin() == end(),直接跳过入堆,否则解引用 end() 会未定义行为。还有:如果你最终要返回一个 std::set,别在归并过程中边生成边 insert,那等于放弃 O(N log k) 优势;应先写入 std::vector,再用 std::set<int>(result.begin(), result.end()) 一次性构造。



















