查找算法:顺序查找、二分查找、插值查找、斐波那契查找

查找,说白了就是在一堆数据里找到你想要的那个。我做了这么多年嵌入式开发,几乎每个项目都离不开查找。从传感器数据表到配置参数,从通信协议到内存管理,查找算法无处不在。

今天咱们就把四种最经典的查找算法掰开揉碎了讲。顺序查找、二分查找、插值查找、斐波那契查找,它们各有各的脾气,也各有各的用武之地。

查找算法知识体系 查找算法 顺序查找 O(n) 无要求 二分查找 O(log n) 有序 插值查找 O(log log n) 均匀分布 斐波那契查找 O(log n) 有序 无除法 数据规模与分布特性决定算法选择

1. 顺序查找——最朴实的办法

顺序查找,就是从头到尾挨个比。你想想看,如果给你一摞没排序的试卷让你找张三的,你只能一张一张翻,对吧?这就是顺序查找。

它的代码简单到不能再简单:

int sequential_search(int arr[], int n, int key) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == key) {
            return i;  // 找到了,返回下标
        }
    }
    return -1;  // 没找到
}
我的经验:在嵌入式项目中,如果数据量小于50个,我一般直接用顺序查找。代码简单,不容易出bug,而且现代CPU的缓存预取机制让顺序访问非常快。别小看它,有时候简单就是王道。

时间复杂度:最好O(1),最坏O(n),平均O(n)。空间复杂度O(1)。

2. 二分查找——有序世界的利器

二分查找的前提是数据必须有序。它的思路很简单:每次取中间值,比目标大就往左找,比目标小就往右找。每次砍掉一半数据。

我记得刚入行时,有个同事在10000个有序数据里用顺序查找找配置项,每次要等好几秒。我帮他改成二分查找后,瞬间就出结果了。他当时那个表情,我到现在还记得。

int binary_search(int arr[], int n, int key) {
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;  // 防止溢出
        if (arr[mid] == key) {
            return mid;
        } else if (arr[mid] < key) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;
}
注意:mid = (left + right) / 2 这种写法在 left 和 right 都很大的时候可能溢出。我习惯用 left + (right - left) / 2,安全又高效。

时间复杂度O(log n)。1000个数据最多查10次,100万个数据最多查20次。这就是二分查找的魅力。

3. 插值查找——更聪明的二分

插值查找是二分查找的升级版。二分查找死板地取中间,插值查找会根据目标值大概在什么位置来猜。

打个比方:你在字典里查「张」字,你会从中间翻吗?不会,你会从后面翻。插值查找就是这个思路。

int interpolation_search(int arr[], int n, int key) {
    int left = 0, right = n - 1;
    while (left <= right && key >= arr[left] && key <= arr[right]) {
        // 插值公式:按比例估算位置
        int pos = left + (key - arr[left]) * (right - left) / (arr[right] - arr[left]);
        if (arr[pos] == key) {
            return pos;
        } else if (arr[pos] < key) {
            left = pos + 1;
        } else {
            right = pos - 1;
        }
    }
    return -1;
}
关键点:插值查找要求数据分布均匀。如果数据是1, 2, 3, ..., 100这种等差数列,它一次就能命中。但如果数据是1, 2, 100, 1000, 10000这种分布极不均匀的,它可能比二分还慢。

我曾经在一个温度传感器数据表里用过插值查找。温度值在-40°C到85°C之间均匀分布,效果非常好,平均查找次数比二分少了将近一半。

4. 斐波那契查找——没有除法的二分

斐波那契查找,说白了就是用斐波那契数列来分割数组,而不是用除法。为什么不用除法?因为在一些老式的嵌入式芯片上,除法指令很慢,甚至没有硬件除法器。

它的核心思想:用斐波那契数F(k)作为分割点,每次把数组分成F(k-1)和F(k-2)两部分。

int fibonacci_search(int arr[], int n, int key) {
    // 生成斐波那契数列,找到第一个大于等于n的F(k)
    int fib1 = 0, fib2 = 1, fib3 = fib1 + fib2;
    while (fib3 < n) {
        fib1 = fib2;
        fib2 = fib3;
        fib3 = fib1 + fib2;
    }
    
    int offset = -1;  // 已排除部分的偏移量
    while (fib3 > 1) {
        int i = (offset + fib1) < (n - 1) ? (offset + fib1) : (n - 1);
        if (arr[i] < key) {
            fib3 = fib2;
            fib2 = fib1;
            fib1 = fib3 - fib2;
            offset = i;
        } else if (arr[i] > key) {
            fib3 = fib1;
            fib2 = fib2 - fib1;
            fib1 = fib3 - fib2;
        } else {
            return i;
        }
    }
    // 检查最后一个元素
    if (fib2 == 1 && arr[offset + 1] == key) {
        return offset + 1;
    }
    return -1;
}
避坑指南:我曾经在一个没有FPU的Cortex-M0芯片上做查找,二分查找里的除法每次要消耗几十个时钟周期。换成斐波那契查找后,虽然查找次数差不多,但每次比较的开销小了很多,整体性能提升了约30%。

四种算法对比

算法 时间复杂度 空间复杂度 数据要求 适用场景
顺序查找 O(n) O(1) 小数据量、无序数据
二分查找 O(log n) O(1) 有序 通用有序查找
插值查找 O(log log n) O(1) 有序且均匀分布 均匀分布的大数据集
斐波那契查找 O(log n) O(1) 有序 嵌入式无除法器环境
我的建议:别盲目追求高级算法。我见过太多人为了炫技,在小数据集上用插值查找,结果性能还不如顺序查找。选算法的原则很简单:数据量小用顺序,数据量大且有序用二分,分布均匀且数据量大用插值,嵌入式低端芯片用斐波那契。

嗯,四种查找算法就讲到这里。每种算法都有自己的性格,也都有自己的舞台。你写代码的时候,多想想你的数据长什么样,你的硬件是什么水平,自然就知道该选哪个了。

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