一、搜索算法是什么?
想象你在图书馆找一本书。最简单的方法是从第一本书开始,一本本翻看(顺序搜索),但如果图书馆有几百万本书,这显然太慢了。聪明的图书馆会给书分类编号(如计算机类在 3 楼 A 区),你可以直接去对应区域找(二分搜索),效率就大大提高了。
搜索算法的本质 :
在一堆数据中找到目标值的位置或判断其是否存在。不同的搜索算法就像不同的 "找东西" 策略,适用于不同的场景。
二、顺序搜索:逐本翻书的新手
故事 :
小明第一次去图书馆,不知道书的分类,只能从第一本书开始,逐本翻看,直到找到《Java 编程从入门到精通》或翻完全部书架。
代码实现:
java
arduino
public class SequentialSearch {
// 在数组中查找目标值,返回索引;未找到返回-1
public static int search(int[] array, int target) {
for (int i = 0; i < array.length; i++) {
if (array[i] == target) {
return i; // 找到目标,返回位置
}
}
return -1; // 未找到
}
public static void main(String[] args) {
int[] bookshelf = {12, 34, 56, 78, 90, 23, 45};
int targetBook = 78;
int position = search(bookshelf, targetBook);
if (position != -1) {
System.out.println("找到书啦!在第 " + position + " 个位置");
} else {
System.out.println("没找到这本书...");
}
}
}
特点:
- 简单直接:适用于任何数据(有序或无序)
- 效率低:最坏情况下需要遍历整个数组(时间复杂度 O (n))
- 适用场景:数据量小、无序或未排序的数据
三、二分搜索:会用目录的聪明人
故事 :
小明第二次去图书馆,学会了先查目录(假设书按编号从小到大排列)。他先看中间的书,如果编号比目标小,就去后半部分找;如果比目标大,就去前半部分找。每次缩小一半范围,直到找到目标。
代码实现:
java
arduino
public class BinarySearch {
// 在有序数组中二分查找目标值
public static int search(int[] sortedArray, int target) {
int left = 0;
int right = sortedArray.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 计算中间位置
if (sortedArray[mid] == target) {
return mid; // 找到目标
} else if (sortedArray[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}
return -1; // 未找到
}
public static void main(String[] args) {
int[] sortedBookshelf = {10, 20, 30, 40, 50, 60, 70};
int targetBook = 50;
int position = search(sortedBookshelf, targetBook);
System.out.println("找到书啦!在第 " + position + " 个位置");
}
}
特点:
- 要求有序:必须先对数据排序
- 效率高:每次排除一半数据,时间复杂度 O (log n)
- 适用场景:大规模有序数据(如字典、索引表)
四、哈希搜索:用 ISBN 快速定位
故事 :
图书馆引入了计算机系统,每本书都有唯一的 ISBN 编号。小明只需在系统中输入 ISBN,系统通过 "魔法公式"(哈希函数)直接计算出这本书在哪个书架的哪个位置,无需翻找。
代码实现:
java
arduino
import java.util.HashMap;
public class HashSearch {
public static void main(String[] args) {
// 创建一个哈希表,键是ISBN,值是书架位置
HashMap<String, Integer> librarySystem = new HashMap<>();
// 添加书籍信息
librarySystem.put("ISBN978-7-121-15535-2", 5); // 计算机类在5号书架
librarySystem.put("ISBN978-7-302-25708-4", 12); // 数学类在12号书架
librarySystem.put("ISBN978-7-111-40701-0", 3); // 物理类在3号书架
// 查找目标书
String targetISBN = "ISBN978-7-302-25708-4";
if (librarySystem.containsKey(targetISBN)) {
int position = librarySystem.get(targetISBN);
System.out.println("找到书啦!在第 " + position + " 号书架");
} else {
System.out.println("图书馆没有这本书...");
}
}
}
特点:
- 极快:平均时间复杂度 O (1),几乎瞬间找到
- 空间换时间:需要额外空间存储哈希表
- 适用场景:高频查找、数据量极大(如数据库索引、缓存系统)
五、广度优先搜索(BFS):逐层探索的迷宫
故事 :
小明在迷宫中找出口,他先尝试所有可能的第一步(上、下、左、右),标记走过的路;如果没找到出口,再尝试所有可能的第二步(从每个第一步的终点出发),以此类推,直到找到出口或遍历完所有路径。
代码实现(简化版) :
java
ini
import java.util.LinkedList;
import java.util.Queue;
public class BFSMazeSearch {
public static void main(String[] args) {
// 0表示通路,1表示墙
int[][] maze = {
{0, 1, 0, 0, 0},
{0, 1, 0, 1, 0},
{0, 0, 0, 0, 0},
{0, 1, 1, 1, 0},
{0, 0, 0, 1, 0}
};
int startX = 0, startY = 0; // 起点
int endX = 4, endY = 4; // 终点
// 使用队列存储待探索的位置
Queue<int[]> queue = new LinkedList<>();
boolean[][] visited = new boolean[maze.length][maze[0].length];
queue.offer(new int[]{startX, startY});
visited[startX][startY] = true;
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上、下、左、右
boolean found = false;
while (!queue.isEmpty()) {
int[] current = queue.poll();
int x = current[0];
int y = current[1];
// 找到出口
if (x == endX && y == endY) {
found = true;
System.out.println("找到出口啦!");
break;
}
// 尝试四个方向
for (int[] dir : directions) {
int newX = x + dir[0];
int newY = y + dir[1];
// 检查是否可以走
if (newX >= 0 && newX < maze.length && newY >= 0 && newY < maze[0].length
&& maze[newX][newY] == 0 && !visited[newX][newY]) {
queue.offer(new int[]{newX, newY});
visited[newX][newY] = true;
}
}
}
if (!found) {
System.out.println("没找到出口...");
}
}
}
特点:
- 逐层探索:适合找最短路径(如迷宫、社交网络关系)
- 用队列实现:先进先出(FIFO)
- 空间需求大:最坏情况下需要存储所有节点
六、深度优先搜索(DFS):一条路走到黑的探险
故事 :
小明在迷宫中找出口,他选择一个方向一直走,直到走不通再返回上一个岔路口,尝试另一个方向。比如先一直向右走,遇到墙再返回,尝试向上走,以此类推,直到找到出口或遍历所有路径。
代码实现(递归版) :
java
ini
public class DFSMazeSearch {
private static boolean found = false; // 标记是否找到出口
public static void main(String[] args) {
int[][] maze = {
{0, 1, 0, 0, 0},
{0, 1, 0, 1, 0},
{0, 0, 0, 0, 0},
{0, 1, 1, 1, 0},
{0, 0, 0, 1, 0}
};
int startX = 0, startY = 0;
int endX = 4, endY = 4;
boolean[][] visited = new boolean[maze.length][maze[0].length];
dfs(maze, startX, startY, endX, endY, visited);
if (found) {
System.out.println("找到出口啦!");
} else {
System.out.println("没找到出口...");
}
}
private static void dfs(int[][] maze, int x, int y, int endX, int endY, boolean[][] visited) {
// 检查是否越界、是墙或已访问
if (x < 0 || x >= maze.length || y < 0 || y >= maze[0].length
|| maze[x][y] == 1 || visited[x][y]) {
return;
}
// 标记为已访问
visited[x][y] = true;
// 找到出口
if (x == endX && y == endY) {
found = true;
return;
}
// 尝试四个方向(上、下、左、右)
dfs(maze, x - 1, y, endX, endY, visited); // 上
if (found) return; // 找到出口,提前返回
dfs(maze, x + 1, y, endX, endY, visited); // 下
if (found) return;
dfs(maze, x, y - 1, endX, endY, visited); // 左
if (found) return;
dfs(maze, x, y + 1, endX, endY, visited); // 右
}
}
特点:
- 一条路走到黑:适合探索所有可能路径(如游戏、回溯算法)
- 用递归或栈实现:后进先出(LIFO)
- 空间需求小:只需存储当前路径
七、总结:搜索算法的选择指南
算法 | 适用场景 | 时间复杂度 | 核心思想 |
---|---|---|---|
顺序搜索 | 无序数据、小规模 | O(n) | 逐个比较,简单直接 |
二分搜索 | 有序数据、大规模 | O(log n) | 每次排除一半数据 |
哈希搜索 | 高频查找、数据量大 | O(1) | 哈希函数直接映射位置 |
BFS(广度优先) | 最短路径、分层数据(如社交网络) | O(V+E) | 逐层探索,用队列实现 |
DFS(深度优先) | 所有路径探索、游戏回溯 | O(V+E) | 一条路走到黑,用递归或栈实现 |
记忆口诀:
-
数据无序用顺序
-
数据有序用二分
-
高频查找用哈希
-
最短路径用 BFS
-
路径探索用 DFS
掌握这些搜索算法,就像学会了不同的 "找东西" 技巧,可以根据实际场景选择最高效的方法。这是算法学习的基础,也是解决复杂问题的关键。