查找算法:顺序查找、二分查找、哈希查找,哈希冲突解决

查找,说白了就是在一堆数据里找到你想要的那个。听起来简单,但实际写代码时,不同场景下的选择天差地别。我见过不少新手,上来就用顺序查找遍历几万条数据,结果程序卡得不行。也见过有人盲目用哈希表,结果内存爆了。

今天咱们就把三种主流查找算法掰开揉碎讲清楚。我会结合自己踩过的坑,告诉你什么时候该用哪个。

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;  // 没找到
}

时间复杂度是 O(n),空间复杂度 O(1)。

我的经验: 在嵌入式开发中,如果数据量小于100条,我通常直接用顺序查找。因为代码简单,不容易出bug,而且现代CPU的缓存预取机制让顺序访问非常快。

适用场景:

  • 数据量小(几十到几百条)
  • 数据无序,且不打算排序
  • 只需要查找一次,不值得建索引

2. 二分查找——前提是数据有序

二分查找的核心思想是「每次砍掉一半」。但有个硬性前提:数据必须是有序的。

// 二分查找(非递归版本)
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;
}

时间复杂度 O(log n),空间复杂度 O(1)。

我曾经踩过的坑: 有一次在项目中用二分查找,数据量大概10万条。我直接用 (left+right)/2 计算mid,结果left和right都接近INT_MAX时直接溢出了。后来改成 left + (right-left)/2 才解决。这个细节,面试也常考。

适用场景:

  • 数据量大且有序
  • 查找操作频繁,但插入删除少
  • 内存有限,不能建哈希表

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

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

// 简单的哈希查找(链地址法)
#define TABLE_SIZE 100

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

Node *hash_table[TABLE_SIZE];

int hash_func(int key) {
    return key % TABLE_SIZE;
}

int hash_search(int key) {
    int index = hash_func(key);
    Node *cur = hash_table[index];
    while (cur) {
        if (cur->key == key) {
            return cur->value;
        }
        cur = cur->next;
    }
    return -1;  // 没找到
}

平均时间复杂度 O(1),最坏情况 O(n)。

核心要点: 哈希查找快就快在「直接定位」,不需要比较。但代价是额外的内存开销,以及需要处理哈希冲突。

4. 哈希冲突的解决——躲不开的问题

哈希冲突,说白了就是两个不同的key算出了同一个位置。我刚开始学的时候觉得这概率很小,直到自己写了一个简单的哈希表,数据量一上去,冲突就频繁出现。

常见的解决方法有四种:

方法 原理 优点 缺点
链地址法 每个槽位挂一个链表 实现简单,删除方便 需要额外指针内存
开放地址法 冲突后找下一个空位 空间利用率高 删除麻烦,容易聚集
再哈希法 用另一个哈希函数 分布更均匀 计算开销大
公共溢出区 冲突数据放另一个表 结构清晰 溢出区可能成为瓶颈

我个人最常用的是链地址法。原因很简单:在嵌入式系统中,删除操作很常见,链地址法删除最方便。而且链表节点可以复用内存池,避免碎片化。

避坑指南: 我曾经在一个项目中用开放地址法,结果频繁的插入删除导致大量「伪删除」标记,查找效率急剧下降。后来改成链地址法,问题就解决了。所以如果你的场景删除操作多,优先考虑链地址法。

5. 三种查找算法的对比

算法 时间复杂度 空间复杂度 数据要求 适用场景
顺序查找 O(n) O(1) 小数据量,无序
二分查找 O(log n) O(1) 有序 大数据量,静态数据
哈希查找 O(1) 平均 O(n) 需哈希函数 高频查找,动态数据

6. 知识体系图

下面这张图帮你理清三种查找算法的核心逻辑和选择路径:

查找算法知识体系 查找算法 顺序查找 二分查找 哈希查找 特点 • 无需排序 • 时间复杂度 O(n) • 适合小数据量 特点 • 数据必须有序 • 时间复杂度 O(log n) • 适合静态数据 特点 • 需要哈希函数 • 平均 O(1) 查找 • 空间换时间 哈希冲突解决 • 链地址法 • 开放地址法 • 再哈希法 / 公共溢出区 选择建议 数据小 → 顺序查找 | 数据大且有序 → 二分查找 | 高频动态 → 哈希查找

7. 实际项目中的选择策略

说了这么多理论,咱们聊聊实际项目中怎么选。我总结了一个简单的决策流程:

  1. 先看数据量:小于100条?顺序查找就够了,别折腾。
  2. 再看数据是否有序:如果数据本身有序,或者可以一次性排序,二分查找是性价比最高的选择。
  3. 最后看查找频率:如果查找非常频繁,而且数据动态变化(插入删除多),那就上哈希表。

我的一个真实案例: 之前做一个物联网网关项目,需要根据设备ID快速查找配置信息。设备数量大概5000个,查找频率很高,而且设备会动态上线离线。我一开始用了二分查找,每次插入都要重新排序,性能扛不住。后来改成哈希表(链地址法),查找时间从微秒级降到纳秒级,插入也变成了O(1)。

但代价是内存多了大概20KB。对于那个MCU来说,20KB换性能,值了。

嗯,这就是三种查找算法的核心内容。记住一点:没有最好的算法,只有最适合场景的算法。你想想看,顺序查找虽然慢,但代码简单、零依赖,在某些场景下反而是最稳的选择。


专注资料整理