查找算法:线性查找、二分查找、哈希查找

查找,说白了就是在一堆数据里找到你想要的那个。听起来简单吧?但实际写代码时,不同场景下的查找效率能差出几个数量级。我早年做嵌入式项目时,就因为在循环里用了低效的查找方式,导致整个系统响应延迟,被客户追着改了好几个版本。

今天咱们就把三种最基础的查找算法掰开揉碎讲清楚。线性查找、二分查找、哈希查找,这三种方法各有各的脾气,用对了地方就是神器,用错了就是灾难。

核心要点:查找算法的本质是「空间换时间」或「时间换空间」的权衡。没有绝对的好坏,只有合不合适的场景。

查找算法知识体系 线性查找 二分查找 哈希查找 特性 • 无序数据可用 • 时间复杂度 O(n) • 无需额外空间 特性 • 必须有序数据 • 时间复杂度 O(log n) • 递归/迭代实现 特性 • 需要哈希函数 • 时间复杂度 O(1) • 空间换时间 适用:小数据量、无序数据 适用:有序数据、静态数据集 适用:快速查找、键值对场景 选择依据:数据是否有序?数据量多大?内存是否充足?

一、线性查找——最朴素的暴力美学

线性查找,也叫顺序查找。它的逻辑简单到令人发指:从第一个元素开始,挨个比较,直到找到目标值或者遍历完整个数组。

你可能会想:「这也算算法?」嗯,算。而且在实际项目中,它往往是最先被想到的方案。我记得有一次调试一个传感器数据采集程序,数据量只有几十个字节,我直接用线性查找,代码写起来快,调试也方便。

// 线性查找 - 最基础的实现
int linear_search(int arr[], int n, int target) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == target) {
            return i;  // 找到了,返回索引
        }
    }
    return -1;  // 没找到
}

小技巧:如果你确定数据中一定存在目标值,可以省略最后的 -1 判断。但我不建议这么做——万一数据出问题,返回一个错误索引比返回 -1 更难排查。

线性查找的时间复杂度是 O(n)。说白了,数据量翻倍,查找时间也翻倍。在数据量小于 100 的场景下,这完全够用。但如果你在 10 万条数据里用线性查找,那体验就有点糟糕了。

注意:我曾经在一个循环中嵌套了线性查找,外层循环 1000 次,内层查找 5000 条数据。结果程序跑一次要 5 秒。后来改成哈希查找,瞬间降到 10 毫秒。所以,线性查找虽好,可不要贪杯哦。

二、二分查找——有序世界的快速通道

二分查找的前提很苛刻:数据必须有序。但一旦满足这个条件,它的效率就非常可观了。每次比较都能排除一半的数据,时间复杂度只有 O(log n)。

你想想看,在 100 万条有序数据里找某个值,线性查找最多要比较 100 万次,而二分查找最多只需要 20 次。这就是差距。

// 二分查找 - 迭代实现
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 位嵌入式系统上就踩过这个坑,数组索引超过 2^31 时直接崩了。

二分查找也可以用递归实现,代码更简洁,但递归调用有栈开销。在嵌入式环境里,我一般用迭代版本,省栈空间,也更容易控制。

// 二分查找 - 递归实现(了解即可)
int binary_search_recursive(int arr[], int left, int right, int target) {
    if (left > right) return -1;
    
    int mid = left + (right - left) / 2;
    
    if (arr[mid] == target) return mid;
    if (arr[mid] < target) 
        return binary_search_recursive(arr, mid + 1, right, target);
    else 
        return binary_search_recursive(arr, left, mid - 1, target);
}

避坑指南:二分查找的边界条件非常容易写错。我建议你记住一个原则:while (left <= right) 配合 left = mid + 1right = mid - 1。这个组合最不容易出错。如果你用 left < right,那就要小心处理边界了。

三、哈希查找——用空间换时间的极致

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

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

// 简单的哈希表实现 - 链地址法解决冲突
#include <stdlib.h>
#include <string.h>

#define TABLE_SIZE 100

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

typedef struct {
    Node** buckets;
    int size;
} HashTable;

// 哈希函数 - 取模法
int hash_func(int key, int size) {
    return abs(key) % size;
}

// 创建哈希表
HashTable* create_table(int size) {
    HashTable* table = (HashTable*)malloc(sizeof(HashTable));
    table->size = size;
    table->buckets = (Node**)calloc(size, sizeof(Node*));
    return table;
}

// 插入键值对
void insert(HashTable* table, int key, int value) {
    int index = hash_func(key, table->size);
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->key = key;
    new_node->value = value;
    new_node->next = table->buckets[index];
    table->buckets[index] = new_node;
}

// 查找
int search(HashTable* table, int key) {
    int index = hash_func(key, table->size);
    Node* current = table->buckets[index];
    
    while (current != NULL) {
        if (current->key == key) {
            return current->value;
        }
        current = current->next;
    }
    return -1;  // 没找到
}

个人经验:哈希函数的选择很关键。取模法虽然简单,但如果 key 分布不均匀,会导致大量冲突。我在项目中常用的是「乘法取整法」或者对字符串用「BKDR 哈希」。记住一点:好的哈希函数能让数据均匀分布,减少链表长度。

哈希查找的缺点也很明显:需要额外的内存空间来存储哈希表。在嵌入式系统中,如果内存紧张,哈希表可能不是最佳选择。另外,哈希表不支持范围查找——你想找「大于 100 的所有元素」?哈希表做不到。

三种算法对比

算法 时间复杂度 空间复杂度 数据要求 适用场景
线性查找 O(n) O(1) 无要求 小数据量、无序数据
二分查找 O(log n) O(1) 必须有序 有序数据、静态数据集
哈希查找 O(1) 平均 O(n) 需要哈希函数 快速查找、键值对场景

我的建议:在实际项目中,不要一开始就追求「最高效」的算法。先评估数据量和场景。数据量小于 1000 且查找不频繁?线性查找就够了。数据有序且查找频繁?二分查找是首选。需要极速查找且内存充足?哈希表是你的好帮手。

这三种查找算法是基础中的基础。你可能会觉得它们太简单,但说实话,我在工作中见过太多因为选错查找算法而导致的性能问题。把这三个吃透了,你就能应对 90% 以上的查找场景。


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