常用查找算法:find、find_if、adjacent_find、binary_search、count、count_if
查找,说白了就是在一堆数据里找到你想要的那个。我做了这么多年C++开发,可以说查找操作几乎每天都在写。STL给我们准备了一套非常趁手的查找工具,用好了能省不少事。
今天咱们就把这六个常用查找算法一次性讲透。它们分别是:find、find_if、adjacent_find、binary_search、count、count_if。每个都有自己擅长的场景,咱们一个一个来看。
核心要点:这六个算法都定义在 <algorithm> 头文件中。它们操作的是迭代器区间,不直接操作容器。记住这一点,你就能理解为什么它们这么灵活。
1. find — 最简单的值查找
find 干的事很纯粹:在区间里找第一个等于某个值的元素。找到了就返回指向它的迭代器,找不到就返回区间的 end。
我个人习惯在写原型代码时先用 find,因为它最直观,不容易出错。
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};
auto it = std::find(vec.begin(), vec.end(), 5);
if (it != vec.end()) {
std::cout << "找到了: " << *it << std::endl;
} else {
std::cout << "没找到" << std::endl;
}
return 0;
}
小提示:find 内部用的是 operator== 来比较。如果你存的是自定义类型,记得重载 operator==,不然编译器会报错。
2. find_if — 按条件查找
有时候你要找的不是某个具体的值,而是满足某个条件的元素。比如找第一个大于10的数,或者找第一个名字以"A"开头的人。这时候 find_if 就派上用场了。
它接受一个谓词(predicate),也就是一个返回 bool 的函数或 lambda。它会遍历区间,对每个元素调用这个谓词,直到返回 true。
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> scores = {58, 72, 45, 88, 63, 91};
// 找第一个及格(>=60)的成绩
auto it = std::find_if(scores.begin(), scores.end(),
[](int s) { return s >= 60; });
if (it != scores.end()) {
std::cout << "第一个及格成绩: " << *it << std::endl;
}
return 0;
}
我在项目中遇到过这样一个场景:要从日志里找出第一条错误级别为 FATAL 的记录。用 find_if 配合一个 lambda,三行代码搞定。要是自己手写循环,至少得七八行,还容易写错边界条件。
3. adjacent_find — 找相邻重复元素
这个算法有点意思。它找的是相邻且相等的两个元素。比如数组 [1, 2, 2, 3, 4, 4, 5] 里,它会找到第一对相邻的 2。
你想想看,什么时候会用上它?我举几个例子:
- 检查排序后的数据里有没有重复值
- 在时间序列数据里找连续相同的记录
- 图像处理中找相邻像素颜色相同的区域
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 3, 3, 5, 7, 7, 9};
auto it = std::adjacent_find(data.begin(), data.end());
if (it != data.end()) {
std::cout << "找到相邻重复: " << *it << std::endl;
}
// 也可以自定义比较规则
auto it2 = std::adjacent_find(data.begin(), data.end(),
[](int a, int b) { return b - a == 2; });
if (it2 != data.end()) {
std::cout << "找到差值为2的相邻元素: " << *it2 << " 和 " << *(it2 + 1) << std::endl;
}
return 0;
}
注意:adjacent_find 默认使用 operator== 比较。如果你传了自定义比较函数,它比较的是「是否满足条件」,而不是「是否相等」。这一点容易搞混,我刚开始用的时候就踩过这个坑。
4. binary_search — 二分查找
这个算法名字起得很直白——二分查找。但它有个大前提:数据必须是已排序的。如果你给一个乱序的区间,结果就是未定义的,可能找到也可能找不到。
它返回的是 bool 值,告诉你目标值存不存在。注意,它不返回位置。如果你需要位置,得用 lower_bound 或 equal_range。
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> sorted = {1, 3, 5, 7, 9, 11, 13};
bool found = std::binary_search(sorted.begin(), sorted.end(), 7);
std::cout << (found ? "找到了" : "没找到") << std::endl;
// 在乱序数据上使用,结果不可靠!
std::vector<int> unsorted = {5, 3, 1, 7, 9};
bool result = std::binary_search(unsorted.begin(), unsorted.end(), 3);
// 结果可能是 false,即使 3 确实在容器里
return 0;
}
我曾经在一个性能敏感的项目里,用 binary_search 替代了 find,查找时间从 O(n) 降到了 O(log n)。数据量大的时候,这个差距非常明显。但前提是——你得先排好序。
性能对比:
| 算法 | 时间复杂度 | 前提条件 | 返回值 |
|---|---|---|---|
| find | O(n) | 无 | 迭代器 |
| find_if | O(n) | 无 | 迭代器 |
| adjacent_find | O(n) | 无 | 迭代器 |
| binary_search | O(log n) | 已排序 | bool |
| count | O(n) | 无 | 整数 |
| count_if | O(n) | 无 | 整数 |
5. count — 统计出现次数
count 做的事很简单:遍历区间,数一数有多少个元素等于给定的值。返回一个整数。
嗯,这里要注意:返回类型是 iterator_traits<InputIterator>::difference_type,说白了就是 ptrdiff_t,是个有符号整数。对于大容器,别用 int 去接,可能会溢出。
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 2, 3, 2, 4, 2, 5};
auto cnt = std::count(vec.begin(), vec.end(), 2);
std::cout << "数字2出现了 " << cnt << " 次" << std::endl;
return 0;
}
6. count_if — 按条件统计
和 find_if 类似,count_if 是 count 的条件版本。它统计满足某个条件的元素个数。
举个例子:统计一个班级里成绩优秀(>=90分)的学生人数。
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> grades = {85, 92, 78, 95, 88, 91, 67};
auto excellent = std::count_if(grades.begin(), grades.end(),
[](int g) { return g >= 90; });
std::cout << "优秀人数: " << excellent << std::endl;
// 也可以统计不及格的
auto failed = std::count_if(grades.begin(), grades.end(),
[](int g) { return g < 60; });
std::cout << "不及格人数: " << failed << std::endl;
return 0;
}
实用技巧:如果你既想统计个数,又想拿到这些元素的位置,可以先用 count_if 知道数量,再用 copy_if 把符合条件的元素拷贝出来。两步操作,清晰明了。
避坑指南
讲了这么多,最后分享几个我踩过的坑:
- 迭代器失效:查找算法本身不会修改容器,但如果你在循环里一边查找一边删除元素,迭代器会失效。我曾经在
find之后直接erase,结果程序崩溃。正确的做法是用erase返回的迭代器继续遍历。 - binary_search 的排序前提:这个我前面强调过,但还是要再说一遍。给
binary_search传未排序的数据,结果就是玄学。你可能会觉得「明明有啊,怎么返回 false」,其实是你没排序。 - 自定义类型的比较:如果你用
find或count处理自定义类型,记得重载operator==。不然编译器会给你一堆看不懂的模板错误信息。
好了,这六个查找算法就讲到这里。它们各有各的用武之地,掌握好了,日常开发中的查找需求基本都能轻松应对。