常用查找算法:find、find_if、adjacent_find、binary_search、count、count_if

查找,说白了就是在一堆数据里找到你想要的那个。我做了这么多年C++开发,可以说查找操作几乎每天都在写。STL给我们准备了一套非常趁手的查找工具,用好了能省不少事。

今天咱们就把这六个常用查找算法一次性讲透。它们分别是:findfind_ifadjacent_findbinary_searchcountcount_if。每个都有自己擅长的场景,咱们一个一个来看。

核心要点:这六个算法都定义在 <algorithm> 头文件中。它们操作的是迭代器区间,不直接操作容器。记住这一点,你就能理解为什么它们这么灵活。

常用查找算法知识体系 查找算法 线性查找 find — 查找第一个匹配值 find_if — 按条件查找 adjacent_find — 找相邻重复 二分查找 binary_search — 快速判断存在 计数查找 count — 统计值出现次数 count_if — 按条件统计 三大类:线性查找、二分查找、计数查找,覆盖日常90%的查找需求

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_boundequal_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_ifcount 的条件版本。它统计满足某个条件的元素个数。

举个例子:统计一个班级里成绩优秀(>=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」,其实是你没排序。
  • 自定义类型的比较:如果你用 findcount 处理自定义类型,记得重载 operator==。不然编译器会给你一堆看不懂的模板错误信息。

好了,这六个查找算法就讲到这里。它们各有各的用武之地,掌握好了,日常开发中的查找需求基本都能轻松应对。

公众号:蓝海资料掘金营,微信deep3321