C++includes判断子集关系算法
C++ 中用 std::includes 判断子集关系:别再手写循环了
上周帮同事调一个权限校验模块,发现他写了二十多行 for-loop 去检查“用户拥有的权限集合”是否包含“本次请求所需的权限集合”。我顺手改成一行 std::includes,他盯着屏幕愣了三秒:“这玩意儿还能干这个?”
其实 std::includes 真的被低估了——它不是为“字符串包含”设计的,而是专为有序序列的子集判定而生。可偏偏很多人只在算法题里见过它,一到真实业务场景就绕开,宁可用 std::find_first_of 或嵌套 for,结果逻辑错漏、性能掉档、还难维护。
我们来把它真正用起来。
它到底在做什么?
std::includes 的语义非常干净:
给定两个已排序的范围
[first1, last1)和[first2, last2),判断 第二个范围中的每个元素是否都出现在第一个范围中(允许重复,但不依赖计数,只看存在性)。
注意三个硬前提:
- 两个序列都必须升序排列(默认用
<比较); - 元素类型支持可比较性(无需重载
==,只靠<即可推导“相等”); - 它不关心重复次数:
{1,2,2,3}包含{2,2}返回true;但{1,2,3}包含{2,2}就是false——因为第二个2找不到对应位置。
这恰好契合多数业务中的子集语义:比如权限集合、配置白名单、API 支持的编码格式列表……我们通常只关心“有没有”,而不是“有几个”。
一个真实可用的例子
假设你维护一个日志系统,不同模块上报的日志级别不同:
std::vector<LogLevel> available = {DEBUG, INFO, WARNING, ERROR, FATAL}; // 已排序
std::vector<LogLevel> required = {INFO, WARNING, ERROR};
你想确认当前环境是否支持全部所需级别。手动写?得遍历 required,对每个值在 available 里二分查找——写起来啰嗦,还容易漏掉边界。
而用 std::includes:
std::sort(available.begin(), available.end()); // 确保有序(若已排好可跳过)
std::sort(required.begin(), required.end());
bool ok = std::includes(available.begin(), available.end(),
required.begin(), required.end());
只要 available 是全集、required 是待检子集,且两者有序,这一行就稳了。
关键细节:为什么必须排序?
std::includes 内部用的是双指针归并思想,时间复杂度 O(N + M),比对每个元素单独二分(O(M log N))更优。但它依赖顺序:
- 指针
it1在全集中推进,it2在子集中推进; - 当
*it1 < *it2,说明*it2还没出现,it1++; - 当
*it1 == *it2,匹配成功,it1++且it2++; - 当
*it1 > *it2,说明*it2永远找不到了 → 直接返回false。
没有排序,这套逻辑会当场失效。 曾有同学把 std::includes 用在未排序 vector 上,测试用例全过,上线后偶发崩溃——因为未定义行为(UB)不报错,只悄悄返回错误结果。
自定义比较?完全支持
如果你的元素不是基础类型,比如:
struct Feature {
std::string name;
int version;
};
而你想按 name 判断子集(忽略 version),只需传入自定义谓词:
auto cmp = [](const Feature& a, const Feature& b) {
return a.name < b.name;
};
std::includes(full.begin(), full.end(),
subset.begin(), subset.end(),
cmp);
注意:两个序列必须用同一套比较规则排序,否则结果不可靠。 这不是限制,而是提醒你:子集判定永远依赖你定义的“相等”标准。
常见误区补丁
- ❌ “我用
std::set就不用管排序了?” ——std::set确实自动有序,但std::includes接受任意迭代器,包括set::begin(),没问题。但别忘了:set插入 O(log N),而 vector 排序一次 O(N log N),后续多次includes查询更快。选容器,先想查询频次。 - ❌ “空集合算子集吗?” —— 是的。
std::includes(..., {}, {})恒为true,符合数学定义,也省去空值特判。 - ✅ 如果数据来自外部(如 JSON 解析),排序必须显式做在
includes调用前,别指望“应该已经排好了”。
最后一句实在话
std::includes 不是炫技工具。它解决的是一个具体问题:给定两个有序集合,快速、安全、无歧义地回答‘B 是 A 的子集吗?’。
当你下一次写“检查某组 ID 是否全在缓存里”“验证客户端支持的协议是否被服务端覆盖”“比对用户角色与接口访问策略”,别急着打开编辑器敲 loop——先看看数据是不是有序。如果是,std::includes 就是你该按下的那个键。
它不神秘,不难记,也不需要额外依赖。
它就在 <algorithm> 里,安静等着被认真用一次。


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