最可靠的方法是将候选子集转为集合后逐个检查元素是否存在于全集;若要求频次一致则需用multiset或unordered_map计数;includes需先排序,裸数组须显式传长度。

用 std::set 或 std::unordered_set 判断子集最稳
原生 C++ 数组不带集合语义,直接比对子集容易漏元素或重复计数。最可靠的做法是把“被检查数组”(即候选子集)转成集合,再逐个查它每个元素是否都在“全集数组”里。
关键不是“有没有”,而是“有没有漏”——比如 {1,1,2} 是 {1,2,3} 的子集吗?不是,因为全集只有一个 1,而子集要两个 1。所以要不要去重,取决于你的业务定义:
- 若只关心元素存在性(数学意义的子集),用
std::set或std::unordered_set - 若要求频次一致(多重集/子多重集),得用
std::multiset或std::unordered_map计数
示例(存在性子集):
std::vector<int> sup = {1, 2, 3, 4, 5};
std::vector<int> sub = {2, 4};
std::unordered_set<int> sup_set(sup.begin(), sup.end());
bool is_subset = true;
for (int x : sub) {
if (sup_set.find(x) == sup_set.end()) {
is_subset = false;
break;
}
}
用 std::includes 前必须排序
std::includes 看起来最贴题,但它只对已排序区间有效,且要求两个区间都升序(或同一种自定义序)。直接传未排序的 std::vector 或裸数组,结果不可预测,甚至可能 crash。
立即学习“C++免费学习笔记(深入)”;
常见错误现象:std::includes 返回 false,但你肉眼看明明是子集——八成是没排序。
- 排序开销:O(n log n + m log m),比哈希集合方案贵
- 优势:不用额外内存(原地排),适合小数组或已预排序场景
- 注意:排序会改原数组;如需保留原序,得拷贝一份
正确用法:
std::vector<int> sup = {5, 1, 3, 2, 4};
std::vector<int> sub = {2, 1};
std::sort(sup.begin(), sup.end()); // 必须
std::sort(sub.begin(), sub.end()); // 必须
bool ok = std::includes(sup.begin(), sup.end(), sub.begin(), sub.end());
裸数组(int arr[])怎么处理
裸数组没 size 信息,传给算法前必须手动算长度,否则越界是常态。别依赖 sizeof(arr)/sizeof(arr[0]) 在函数参数里——那会退化成指针,结果永远是 1。
安全做法只有两种:
- 用模板推导长度:
template<size_t n> bool is_subset(int (&sub)[N], int sup[], size_t sup_len)</size_t> - 改用
std::array或std::vector,自带.size()
硬要用裸数组?至少把长度作为参数显式传入:
bool is_subset(int sub[], size_t sub_len, int sup[], size_t sup_len) {
std::unordered_set<int> sup_set(sup, sup + sup_len);
for (size_t i = 0; i < sub_len; ++i) {
if (sup_set.find(sub[i]) == sup_set.end()) return false;
}
return true;
}
性能和边界容易踩的坑
子集判断看似简单,实际在高频调用或大数据量下,几个点特别容易翻车:
-
std::unordered_set构造时如果全集有大量重复,插入开销白费——先去重再建集 - 空子集永远算子集,但有人会忘判
sub.empty()直接进循环,导致逻辑错或崩溃 - 整型溢出或指针比较陷阱:比如用
char*当数组传,结果比的是地址不是值 - 浮点数慎用:直接用
==判相等几乎必错,得用 epsilon 比较,此时std::unordered_set也得配自定义哈希和等价谓词
真正在意性能且数据稳定,可以预建全集的 std::unordered_set 缓存一次,反复查多个子集;但千万别在循环里反复构造它。


















