经典查找算法合集(上)

一、顺序查找

1. 核心原理

顺序查找(又称线性查找)是最基础的查找算法,适用于无需预处理的线性数据结构(如数组、链表)。其原理是:从数据结构的起始位置开始,逐个比较元素与目标值,直到找到匹配项或遍历完所有元素。

2. 算法步骤

  1. 初始化指针:从第一个元素开始扫描。
  2. 遍历比较:逐个比较当前元素与目标值。
  3. 终止条件
    • 找到目标值 → 返回元素位置。
    • 遍历完所有元素未找到 → 返回特定标记(如 -1)。

3. 时间复杂度与空间复杂度

维度

说明

时间复杂度

O(n)(最坏情况需遍历全部元素)

空间复杂度

O(1)(仅需常数级别额外空间存储指针)

最佳情况

O(1)(目标元素在第一个位置)

4. 代码实现

Python :

def sequential_search(arr, target):
    for index in range(len(arr)):
        if arr[index] == target:
            return index  # 找到目标,返回索引
    return -1  # 未找到

# 示例调用
arr = [4, 2, 7, 1, 9, 5]
target = 7
result = sequential_search(arr, target)
print(f"元素 {target} 的索引位置: {result}")  # 输出: 元素 7 的索引位置: 2

Python 优化版(增加监控哨兵,减少循环次数):

def sequential_search_optimized(arr, target):
    arr.append(target)  # 添加哨兵
    index = 0
    while arr[index] != target:
        index += 1
    arr.pop()  # 移除哨兵
    return index if index < len(arr) else -1

java:

public class SequentialSearch {
    public static int search(int[] arr, int target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target) {
                return i;  // 找到目标,返回索引
            }
        }
        return -1;  // 未找到
    }

    public static void main(String[] args) {
        int[] arr = {4, 2, 7, 1, 9, 5};
        int target = 7;
        int result = search(arr, target);
        System.out.println("元素 " + target + " 的索引位置: " + result);  // 输出: 2
    }
}

php :

<?php
function sequentialSearch(array $arr, $target) {
    // 遍历数组元素
    for ($i = 0; $i < count($arr); $i++) {
        if ($arr[$i] == $target) {
            return $i; // 找到目标,返回索引
        }
    }
    return -1; // 未找到
}

// 示例调用
$arr = [4, 2, 7, 1, 9, 5];
$target = 7;
$result = sequentialSearch($arr, $target);
echo "元素 $target 的索引位置: " . $result; // 输出: 元素 7 的索引位置: 2
?>

go:

package main

import "fmt"

func sequentialSearch(arr []int, target int) int {
    // 遍历切片元素
    for i := 0; i < len(arr); i++ {
        if arr[i] == target {
            return i // 找到目标,返回索引
        }
    }
    return -1 // 未找到
}

func main() {
    arr := []int{4, 2, 7, 1, 9, 5}
    target := 7
    result := sequentialSearch(arr, target)
    fmt.Printf("元素 %d 的索引位置: %d", target, result) // 输出: 元素 7 的索引位置: 2
}

5、优缺点分析

优点

缺点

- 实现简单,无需额外数据结构

- 效率低,时间复杂度 O(n)

- 适用于无序数据和小规模数据集

- 数据量大时性能急剧下降

- 无需预处理(如排序)

- 不适合频繁查找的场景

6、适用场景

  • 小规模数据(如数组长度 < 100)。
  • 数据无序且仅需单次查找
  • 链表结构(无法随机访问,必须顺序遍历)。
  • 算法学习或简单原型开发(快速验证逻辑)。
实际应用示例
  • 文本编辑器查找:在未索引的文档中逐行搜索关键词。
  • 通讯录查找:在未排序的联系人列表中按顺序查找姓名。
  • 硬件调试:在内存转储中逐字节查找特定信号值。

二、二分查找

1、原理

二分查找又叫折半查找,对数搜索。

二分查找(Binary Search) 是一种在 有序数组 中快速查找目标值的算法,通过不断将搜索范围减半来定位元素。

核心步骤

  1. 初始化指针:设左指针 left = 0,右指针 right = 数组长度 - 1
  2. 循环查找
    • 计算中间索引 mid = left + (right - left) // 2(避免整数溢出)。
    • 比较 nums[mid] 与目标值 target
      • nums[mid] == target,找到目标,返回索引。
      • nums[mid] < target,目标在右半部分,更新 left = mid + 1
      • nums[mid] > target,目标在左半部分,更新 right = mid - 1
  3. 终止条件:当 left > right 时,未找到目标,返回 -1

示例
在数组 [2, 4, 6, 8, 10] 中查找 8 的过程:

  1. 初始范围left=0, right=4mid=2(值为6) → 6 < 8 → left=3
  2. 新范围left=3, right=4mid=3(值为8) → 找到目标,返回索引3。

图解:

2、 性能分析

  • 时间复杂度O(log n),每次循环将搜索范围缩小一半。
  • 空间复杂度O(1)(迭代实现)或 O(log n)(递归实现)。
  • 稳定性:结果唯一,但要求数组有序。

3、 代码实现

java:

public int binarySearch(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;
}

php:

function binarySearch($nums, $target) {
    $left = 0;
    $right = count($nums) - 1;
    while ($left <= $right) {
        $mid = $left + intdiv(($right - $left), 2);
        if ($nums[$mid] == $target) {
            return $mid;
        } elseif ($nums[$mid] < $target) {
            $left = $mid + 1;
        } else {
            $right = $mid - 1;
        }
    }
    return -1;
}

python:

def binary_search(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = left + (right - left) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

go:

func binarySearch(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right-left)/2
        if nums[mid] == target {
            return mid
        } else if nums[mid] < target {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return -1
}

4、适用场景

  • 有序数组查找:如数据库索引、日志时间戳查询。
  • 边界查找:寻找第一个/最后一个等于目标的位置(需稍作修改)。
  • 数值范围问题:如求平方根(保留整数)、旋转排序数组查找。

5、常见问题

常见问题
  • 重复元素:默认返回任意匹配位置,若需首个/末个位置,需修改逻辑。
  • 整数溢出:计算 mid 时使用 left + (right - left)/2 而非 (left + right)/2
  • 未排序数组:二分查找失效,需先排序(排序成本 O(n log n))。

三、哈希查找

1、原理

哈希查找(Hash Search) 是一种通过哈希函数将键(Key)直接映射到存储位置的数据结构访问技术。核心依赖 哈希表(Hash Table) 实现,由以下部分组成:

  • 哈希函数:将键转换为哈希值(通常为整数),决定数据存储的桶(Bucket)位置。
  • 冲突解决机制:处理不同键映射到同一位置的情况,常用方法包括:
    • 链地址法:每个桶存储链表或红黑树,冲突元素链接在同一个桶内。
    • 开放寻址法:通过线性探测、二次探测等方式寻找下一个可用位置。

操作步骤

  1. 插入:通过哈希函数计算键的桶位置,存入对应位置(解决冲突后)。
  2. 查找:计算键的哈希值,定位桶,解决冲突后找到目标元素。
  3. 删除:类似查找,找到元素后移除。

2、性能分析

  • 时间复杂度
    • 平均情况:O(1)(理想哈希函数,冲突少)。
    • 最坏情况:O(n)(所有键冲突,退化为链表)。
  • 空间复杂度:O(n)(实际需额外空间减少冲突,如负载因子控制)。

3、适用场景

  • 快速查找/插入/删除:如数据库索引、缓存系统(Redis)、字典结构。
  • 去重操作:如统计唯一用户ID。
  • 频率统计:如统计单词出现次数。
  • 不适合场景:有序数据遍历、范围查询。

4、关键优化技术

  1. 负载因子(Load Factor)
    定义:负载因子 = 元素数量 / 桶数量
    通常设置阈值(如0.75),超过时触发扩容(Rehashing)。
  2. 动态扩容
    扩容时重新分配桶,减少冲突概率。
  3. 优质哈希函数
    目标:均匀分布键,减少冲突。常用算法如MurmurHash、SHA-256(加密场景)。

5、代码实现

java(链地址法)

class HashTable {
    private LinkedList<Entry>[] table;
    private int capacity;

    public HashTable(int capacity) {
        this.capacity = capacity;
        table = new LinkedList[capacity];
        for (int i = 0; i < capacity; i++) {
            table[i] = new LinkedList<>();
        }
    }

    private int hash(int key) {
        return key % capacity;
    }

    public void put(int key, String value) {
        int index = hash(key);
        for (Entry entry : table[index]) {
            if (entry.key == key) {
                entry.value = value;
                return;
            }
        }
        table[index].add(new Entry(key, value));
    }

    public String get(int key) {
        int index = hash(key);
        for (Entry entry : table[index]) {
            if (entry.key == key) {
                return entry.value;
            }
        }
        return null;
    }

    static class Entry {
        int key;
        String value;

        Entry(int key, String value) {
            this.key = key;
            this.value = value;
        }
    }
}

php (内置关联数组)

$hashTable = [];
$hashTable["apple"] = 1;
$hashTable["banana"] = 2;

echo $hashTable["apple"] ?? "Not Found";  // 输出 1

python(字典实现):

class HashTable:
    def __init__(self, size=10):
        self.size = size
        self.table = [[] for _ in range(size)]

    def _hash(self, key):
        return hash(key) % self.size

    def put(self, key, value):
        bucket = self.table[self._hash(key)]
        for i, (k, v) in enumerate(bucket):
            if k == key:
                bucket[i] = (key, value)
                return
        bucket.append((key, value))

    def get(self, key):
        bucket = self.table[self._hash(key)]
        for k, v in bucket:
            if k == key:
                return v
        return None

# 示例
ht = HashTable()
ht.put("key1", "value1")
print(ht.get("key1"))  # 输出 value1

go(内置map)

package main

import "fmt"

func main() {
    hashTable := make(map[string]int)
    hashTable["apple"] = 1
    hashTable["banana"] = 2

    value, exists := hashTable["apple"]
    if exists {
        fmt.Println(value) // 输出 1
    }
}

综合对比

算法

时间复杂度

空间复杂度

数据要求

适用场景

顺序查找

O(n)

O(1)

无序

小数据、简单实现

二分查找

O(log n)

O(1)

必须有序

大规模有序数据

哈希表查找

O(1)

O(n)

哈希函数设计

高频查找、无范围查询

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值