查找算法:顺序查找、二分查找、哈希查找

查找,说白了就是在一堆数据里找到你想要的那个。我做了这么多年嵌入式开发,几乎每个项目都离不开查找。小到按键扫描,大到文件系统索引,查找算法的选择直接决定了系统的响应速度。今天咱们就把三种最经典的查找算法掰开揉碎了讲清楚。

1. 顺序查找 —— 最笨但最可靠的方法

顺序查找的思路简单到令人发指:从第一个元素开始,挨个比,直到找到目标值。你想想看,这就像在一堆钥匙里找你家那把,一把一把试。

基本原理:遍历整个数组,逐个比较元素与目标值。时间复杂度 O(n),空间复杂度 O(1)。

适用场景:数据量小(比如几十个元素),或者数据无序。我在调试一个老旧传感器模块时,数据量就十几个字节,用顺序查找完全够用,写起来还快。

核心要点:顺序查找不要求数据有序,实现简单,但效率随数据量线性下降。
// 顺序查找:返回目标值的索引,未找到返回 -1
int sequential_search(int arr[], int n, int target) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == target) {
            return i;   // 找到了,返回位置
        }
    }
    return -1;          // 没找到
}

这段代码我闭着眼都能写。但要注意一点:如果数组很大,比如几千个元素,顺序查找就会很慢。我曾经在一个项目里用顺序查找查一个 2000 个元素的表,结果每次按键响应都卡顿。后来换成二分查找,问题立刻解决。

小技巧:如果数据经常变动(增删改),顺序查找反而有优势——它不需要维护有序性。

2. 二分查找 —— 快就一个字

二分查找的前提是数据必须有序。它的思路是:每次取中间元素比较,如果目标比中间值小,就去左半边找;比中间值大,就去右半边找。每次都能排除一半的数据。

时间复杂度 O(log n),空间复杂度 O(1)(迭代版本)。

为什么这么快? 你想想看,1000 个有序数据,顺序查找最坏要比较 1000 次,二分查找最多比较 10 次(2^10 = 1024)。差距就是这么大。

注意:二分查找的前提是数组必须有序。如果数据无序,先排序再查找,总成本可能比顺序查找还高。我见过有人拿无序数组直接跑二分查找,结果查不到数据还一脸懵——嗯,排序这步不能省。
// 二分查找(迭代版本):返回目标值的索引,未找到返回 -1
int binary_search(int arr[], int n, int target) {
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;  // 防止溢出
        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;
}

这里有个细节:mid = left + (right - left) / 2 而不是 (left + right) / 2。为什么?因为 left + right 可能溢出。我在一个 32 位单片机上吃过这个亏,数组索引用 int,当 left 和 right 都接近 2^31 时,一加就溢出了。后来改成这种写法,再也没出过问题。

避坑指南:我曾经在嵌入式项目中用递归实现二分查找,结果栈空间不够,直接死机。后来全部改成迭代版本,稳得很。嵌入式环境里,递归要慎用。

3. 哈希查找 —— 用空间换时间

哈希查找的思路是:通过一个哈希函数,把关键字直接映射到存储位置。理想情况下,时间复杂度 O(1)。

说白了,就是给每个数据算一个“身份证号”,然后根据这个号直接去对应位置取数据。不需要比较,一步到位。

基本原理

  • 设计哈希函数(比如取模运算)
  • 将关键字映射到数组下标
  • 处理冲突(不同关键字映射到同一位置)

常见的冲突处理方法有:链地址法(每个位置挂一个链表)、开放地址法(找下一个空位)。

核心要点:哈希查找的查找速度极快,但需要额外的存储空间,且哈希函数的设计直接影响性能。
// 简单的哈希查找(链地址法示例)
#include <stdlib.h>

#define TABLE_SIZE 100

typedef struct Node {
    int key;
    int value;
    struct Node *next;
} Node;

Node* hash_table[TABLE_SIZE];

// 哈希函数:取模
int hash(int key) {
    return key % TABLE_SIZE;
}

// 插入
void insert(int key, int value) {
    int index = hash(key);
    Node *new_node = (Node*)malloc(sizeof(Node));
    new_node->key = key;
    new_node->value = value;
    new_node->next = hash_table[index];
    hash_table[index] = new_node;
}

// 查找
int search(int key) {
    int index = hash(key);
    Node *cur = hash_table[index];
    while (cur != NULL) {
        if (cur->key == key) {
            return cur->value;
        }
        cur = cur->next;
    }
    return -1;  // 未找到
}

哈希查找在嵌入式里用得也很多,比如缓存系统、快速查表。但要注意:哈希表会占用额外内存,而且哈希函数如果设计不好,冲突太多,性能会退化到 O(n)。

注意:在内存受限的嵌入式系统中,哈希表可能不是最佳选择。我曾经在一个只有 4KB RAM 的 MCU 上尝试用哈希表,结果内存直接爆了。后来改用二分查找,虽然慢一点,但内存够用。

三种算法对比

算法 时间复杂度 空间复杂度 数据要求 适用场景
顺序查找 O(n) O(1) 数据量小,或数据无序
二分查找 O(log n) O(1) 有序 数据量大且有序,内存有限
哈希查找 O(1) 平均 O(n) 需设计哈希函数 查找频繁,内存充足

知识体系图

下面这张图帮你理清三种查找算法的核心逻辑和选择依据。

查找算法知识体系 查找算法 顺序查找 二分查找 哈希查找 特点: • 不要求数据有序 • 实现简单,O(n) 特点: • 数据必须有序 • 效率高,O(log n) 特点: • 需要哈希函数 • 平均 O(1),空间换时间 选择依据:数据量、有序性、内存限制、查找频率 没有最好的算法,只有最合适的算法

如何选择?

我个人习惯这样选:

  • 数据量小于 50:顺序查找,代码简单,维护方便。
  • 数据量大且有序:二分查找,内存占用小,速度快。
  • 查找非常频繁,内存够用:哈希查找,一步到位。

记住一点:没有银弹。我在一个项目里同时用了二分查找和哈希表——启动时用二分查找加载配置,运行时用哈希表快速查询。各取所长,才是工程之道。

我的经验:在嵌入式开发中,优先考虑二分查找。因为它不额外占用内存,而且 O(log n) 的速度对大多数场景已经足够。哈希表虽然快,但内存开销和哈希函数的设计成本,有时候得不偿失。

好了,三种查找算法就讲到这里。代码都给你了,原理也讲透了,剩下的就是多写多练。遇到实际问题时,想想今天说的这些,选一个最合适的。

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