目录
一、使用场景对比
算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 | 典型使用场景 |
---|---|---|---|---|---|---|
线性搜索 | 逐个遍历,暴力检查每个元素。 | O(n) | O(1) | 实现简单;对数据无任何要求(无序、有序均可)。 | 数据量大时效率极低。 | 1. 数据量很小。 2. 数据集合未排序且仅搜索一次。 3. 在链表等不支持随机访问的数据结构中搜索。 |
二分搜索 | 分而治之,每次比较将搜索范围缩小一半。 | O(log n) | 迭代: O(1) 递归: O(log n) | 效率极高,数据量越大优势越明显。 | 数据必须预先排序;插入/删除数据后维护有序的成本高。 | 1. 大型且已排序的静态数组(很少增删,频繁查找)。 2. 需要在一维有序数据中快速定位元素(如字典、电话簿)。 3. 作为更复杂算法(如二分答案)的基础。 |
深度优先搜索 | 优先沿着一条路径走到尽头,再回溯探索其他分支。 | O(V+E) | O(h) | 内存消耗相对较低(仅存储当前路径);能找到所有解;代码实现(递归)简洁。 | 不一定找到最短路径;递归过深可能导致栈溢出。 | 1. 遍历或搜索树/图的所有节点或路径。 2. 拓扑排序。 3. 解决回溯问题(如八皇后、数独),需要尝试所有可能性。 4. 检测图中是否存在环。 |
广度优先搜索 | 逐层扩散,先访问离起点最近的所有节点。 | O(V+E) | O(w) | 一定能找到无权图的最短路径(按边数计算)。 | 内存消耗大(需要存储每一层的节点)。 | 1. 寻找无权图中的最短路径(如社交网络中的最短关系链)。 2. 树的层级遍历。 3. 网络广播(如洪水填充算法)。 4. 在状态空间中寻找步骤最少的解决方案(如华容道)。 |
DFS与BFS对比
特性 | DFS | BFS |
---|---|---|
数据结构 | 栈 | 队列 |
内存使用 | 较少 | 较多 |
找到的路径 | 不一定最短 | 最短路径 |
适用场景 | 拓扑排序、连通性检测 | 最短路径、层级遍历 |
二、常用搜索算法
2.1 线性搜索
核心思想:从数据结构的起始位置开始,逐个检查每个元素,直到找到目标元素或遍历完所有元素。
时间复杂度:O(n)
空间复杂度:O(1)
// 基础实现(整数数组)
public static int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i; // 找到目标,返回索引
}
}
return -1; // 未找到目标
}
// 泛型实现(支持任何对象类型)
public static <T> int linearSearch(T[] arr, T target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i].equals(target)) {
return i;
}
}
return -1;
}
2.2 二分搜索
核心思想:要求数据集必须是有序的(升序或降序),通过不断将搜索区间减半来快速定位目标元素。
时间复杂度:O(log n)
空间复杂度:O(1)(迭代版本)或 O(log n)(递归版本)
public class BinarySearch {
// 迭代方式
public static int binarySearchIterative(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (arr[mid] == target) {
return mid;
} else if (target < arr[mid]) {
right = mid - 1; // 搜索左半部分
} else {
left = mid + 1; // 搜索右半部分
}
}
return -1;
}
// 递归方式
public static int binarySearchRecursive(int[] arr, int target, int left, int right) {
// 基线条件:区间无效,说明未找到
if (left > right) {
return -1;
}
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (target < arr[mid]) {
// 递归调用,搜索左半部分
return binarySearchRecursive(arr, target, left, mid - 1);
} else {
// 递归调用,搜索右半部分
return binarySearchRecursive(arr, target, mid + 1, right);
}
}
}
2.3 DFS:深度优先搜索
核心思想:尽可能深地探索树的分支,直到到达叶子节点,然后回溯。是一种用于遍历或搜索树或图的算法。
-
从根节点(或任意节点)开始。
-
访问该节点,并将其标记为已访问。
-
递归地访问该节点的每一个未访问过的相邻节点。
时间复杂度:O(V+E),其中V是顶点数,E是边数
空间复杂度:O(V)
2.3.1 二叉树的遍历(前/中/后序遍历)
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public class DFS {
// 前序遍历 (Pre-order): 根 -> 左 -> 右
public static void dfsPreOrder(TreeNode node) {
if (node == null) return;
System.out.print(node.val + " "); // 访问根节点
dfsPreOrder(node.left); // 遍历左子树
dfsPreOrder(node.right); // 遍历右子树
}
// 中序遍历 (In-order): 左 -> 根 -> 右
public static void dfsInOrder(TreeNode node) {
if (node == null) return;
dfsInOrder(node.left); // 遍历左子树
System.out.print(node.val + " "); // 访问根节点
dfsInOrder(node.right); // 遍历右子树
}
// 后序遍历 (Post-order): 左 -> 右 -> 根
public static void dfsPostOrder(TreeNode node) {
if (node == null) return;
dfsPostOrder(node.left); // 遍历左子树
dfsPostOrder(node.right); // 遍历右子树
System.out.print(node.val + " "); // 访问根节点
}
public static void main(String[] args) {
// 构建一个简单的二叉树
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
System.out.println("前序遍历:");
dfsPreOrder(root); // 输出: 1 2 4 5 3
System.out.println("\n中序遍历:");
dfsInOrder(root); // 输出: 4 2 5 1 3
System.out.println("\n后序遍历:");
dfsPostOrder(root); // 输出: 4 5 2 3 1
}
}
2.3.2 图的遍历(邻接表):递归
import java.util.*;
// 图类
class Graph {
private int V; // 顶点数
private LinkedList<Integer> adj[]; // 邻接表
// 构造函数
Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
// 添加边
void addEdge(int v, int w) {
adj[v].add(w);
}
// DFS递归实现
void DFSUtil(int v, boolean visited[]) {
// 标记当前节点为已访问并输出
visited[v] = true;
System.out.print(v + " ");
// 递归访问所有未访问的邻接节点
Iterator<Integer> i = adj[v].listIterator();
while (i.hasNext()) {
int n = i.next();
if (!visited[n])
DFSUtil(n, visited);
}
}
// DFS遍历入口
void DFS(int v) {
// 标记所有顶点为未访问
boolean visited[] = new boolean[V];
// 调用递归辅助函数
DFSUtil(v, visited);
}
// 处理不连通图的DFS
void DFS() {
boolean visited[] = new boolean[V];
for (int i = 0; i < V; ++i)
if (!visited[i])
DFSUtil(i, visited);
}
}
public class DFSExample {
public static void main(String args[]) {
Graph g = new Graph(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
System.out.println("从顶点2开始的深度优先遍历:");
g.DFS(2);
System.out.println("\n整个图的深度优先遍历:");
g.DFS();
}
}
2.3.3 图的遍历:迭代(栈)
import java.util.*;
class Graph {
private int V;
private LinkedList<Integer> adj[];
Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
void addEdge(int v, int w) {
adj[v].add(w);
}
// 迭代DFS实现
void DFSIterative(int s) {
boolean visited[] = new boolean[V];
Stack<Integer> stack = new Stack<>();
stack.push(s);
while (!stack.empty()) {
s = stack.pop();
if (!visited[s]) {
System.out.print(s + " ");
visited[s] = true;
}
Iterator<Integer> itr = adj[s].iterator();
while (itr.hasNext()) {
int n = itr.next();
if (!visited[n])
stack.push(n);
}
}
}
}
public class DFSIterativeExample {
public static void main(String args[]) {
Graph g = new Graph(5);
g.addEdge(1, 0);
g.addEdge(0, 2);
g.addEdge(2, 1);
g.addEdge(0, 3);
g.addEdge(1, 4);
System.out.println("迭代DFS遍历:");
g.DFSIterative(0);
}
}
2.3.4 图:查找路线
import java.util.*;
class Graph {
private int V;
private LinkedList<Integer> adj[];
Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
void addEdge(int v, int w) {
adj[v].add(w);
}
// 查找从s到d的路径
void findPath(int s, int d) {
boolean visited[] = new boolean[V];
ArrayList<Integer> pathList = new ArrayList<>();
// 添加源节点到路径
pathList.add(s);
// 递归查找所有路径
findPathUtil(s, d, visited, pathList);
}
private void findPathUtil(Integer u, Integer d, boolean visited[], List<Integer> pathList) {
// 标记当前节点
visited[u] = true;
if (u.equals(d)) {
System.out.println(pathList);
// 取消标记当前节点,以便查找其他路径
visited[u] = false;
return;
}
// 递归所有邻接节点
for (Integer i : adj[u]) {
if (!visited[i]) {
pathList.add(i);
findPathUtil(i, d, visited, pathList);
pathList.remove(i); // 回溯
}
}
// 取消标记当前节点
visited[u] = false;
}
}
public class DFSPathExample {
public static void main(String args[]) {
Graph g = new Graph(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(0, 3);
g.addEdge(2, 0);
g.addEdge(2, 1);
g.addEdge(1, 3);
int s = 2, d = 3;
System.out.println("从 " + s + " 到 " + d + " 的路径:");
g.findPath(s, d);
}
}
2.3.5 检测图中的环
import java.util.*;
class Graph {
private int V;
private LinkedList<Integer> adj[];
Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
void addEdge(int v, int w) {
adj[v].add(w);
}
// 检测图中是否有环
boolean isCyclic() {
boolean visited[] = new boolean[V];
boolean recStack[] = new boolean[V];
for (int i = 0; i < V; i++)
if (isCyclicUtil(i, visited, recStack))
return true;
return false;
}
private boolean isCyclicUtil(int i, boolean visited[], boolean recStack[]) {
if (recStack[i])
return true;
if (visited[i])
return false;
visited[i] = true;
recStack[i] = true;
List<Integer> children = adj[i];
for (Integer c : children)
if (isCyclicUtil(c, visited, recStack))
return true;
recStack[i] = false;
return false;
}
}
public class DFSCycleExample {
public static void main(String args[]) {
Graph g = new Graph(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
if (g.isCyclic())
System.out.println("图中有环");
else
System.out.println("图中无环");
}
}
2.3.6 图:拓扑排序
import java.util.*;
class Graph {
private int V;
private LinkedList<Integer> adj[];
Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
void addEdge(int v, int w) {
adj[v].add(w);
}
// 拓扑排序
void topologicalSort() {
Stack<Integer> stack = new Stack<>();
boolean visited[] = new boolean[V];
for (int i = 0; i < V; i++)
if (!visited[i])
topologicalSortUtil(i, visited, stack);
// 打印拓扑排序结果
while (!stack.empty())
System.out.print(stack.pop() + " ");
}
private void topologicalSortUtil(int v, boolean visited[], Stack<Integer> stack) {
visited[v] = true;
for (Integer i : adj[v]) {
if (!visited[i])
topologicalSortUtil(i, visited, stack);
}
stack.push(v);
}
}
public class DFSTopologicalSort {
public static void main(String args[]) {
Graph g = new Graph(6);
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
System.out.println("拓扑排序:");
g.topologicalSort();
}
}
2.4 BFS:广度优先搜索
核心思想:从根节点开始,逐层地访问所有相邻节点,然后再继续向下层扩展。通常使用队列 (Queue) 来实现。
-
将根节点放入队列。
-
从队列中取出第一个节点并访问它。
-
将这个节点的所有未访问过的相邻节点(例如,二叉树的左右子节点)加入队列。
-
重复步骤 2 和 3,直到队列为空。
时间复杂度:O(V+E),其中V是顶点数,E是边数
空间复杂度:O(V),最坏情况下需要存储所有顶点
2.4.1 二叉树的遍历(层序遍历)
import java.util.LinkedList;
import java.util.Queue;
public class BFS {
public static void bfs(TreeNode root) {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root); // 将根节点加入队列
while (!queue.isEmpty()) {
TreeNode currentNode = queue.poll(); // 取出并移除队列头部的节点
System.out.print(currentNode.val + " "); // 访问该节点
// 将当前节点的子节点加入队列
if (currentNode.left != null) {
queue.offer(currentNode.left);
}
if (currentNode.right != null) {
queue.offer(currentNode.right);
}
}
}
public static void main(String[] args) {
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
System.out.println("广度优先遍历 (层级遍历):");
bfs(root); // 输出: 1 2 3 4 5 6
}
}
2.4.2 图的遍历
import java.util.*;
// 图的类表示
class Graph {
private int V; // 顶点数
private LinkedList<Integer> adj[]; // 邻接表
// 构造函数
Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
// 添加边
void addEdge(int v, int w) {
adj[v].add(w);
}
// BFS遍历
void BFS(int s) {
// 标记所有顶点为未访问(false)
boolean visited[] = new boolean[V];
// 创建BFS队列
LinkedList<Integer> queue = new LinkedList<Integer>();
// 标记当前节点为已访问并加入队列
visited[s] = true;
queue.add(s);
while (queue.size() != 0) {
// 从队列中取出一个顶点
s = queue.poll();
System.out.print(s + " ");
// 获取所有相邻顶点
Iterator<Integer> i = adj[s].listIterator();
while (i.hasNext()) {
int n = i.next();
// 如果相邻顶点未被访问,则标记并加入队列
if (!visited[n]) {
visited[n] = true;
queue.add(n);
}
}
}
}
public static void main(String args[]) {
Graph g = new Graph(4);
// 添加边
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
System.out.println("从顶点2开始的广度优先遍历:"); // 2 0 3 1
g.BFS(2);
}
}
2.4.3 图的层次遍历
// 在Graph类中添加方法
void BFSTraversalWithLevels(int s) {
boolean[] visited = new boolean[V];
LinkedList<Integer> queue = new LinkedList<>();
visited[s] = true;
queue.add(s);
int level = 0;
while (!queue.isEmpty()) {
System.out.print("层级 " + level + ": ");
int levelSize = queue.size();
// 处理当前层级的所有节点
for (int i = 0; i < levelSize; i++) {
int current = queue.poll();
System.out.print(current + " ");
// 添加下一层级的节点
for (int neighbor : adj[current]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.add(neighbor);
}
}
}
System.out.println();
level++;
}
}
2.4.4 图:寻找最短路径
// 在Graph类中添加方法
void BFSShortestPath(int s, int d) {
// 存储每个顶点是否被访问
boolean[] visited = new boolean[V];
// 存储每个顶点的前驱节点
int[] predecessor = new int[V];
// 存储每个顶点到起点的距离
int[] distance = new int[V];
// 初始化数组
Arrays.fill(visited, false);
Arrays.fill(distance, -1);
Arrays.fill(predecessor, -1);
LinkedList<Integer> queue = new LinkedList<>();
// 起点处理
visited[s] = true;
distance[s] = 0;
queue.add(s);
while (!queue.isEmpty()) {
int current = queue.poll();
// 遍历所有相邻节点
for (int neighbor : adj[current]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
distance[neighbor] = distance[current] + 1;
predecessor[neighbor] = current;
queue.add(neighbor);
// 如果找到目标节点
if (neighbor == d) {
// 打印最短路径
printPath(predecessor, s, d);
System.out.println("\n最短距离: " + distance[d]);
return;
}
}
}
}
System.out.println("没有从 " + s + " 到 " + d + " 的路径");
}
// 辅助方法:打印路径
void printPath(int[] predecessor, int s, int d) {
if (d == s) {
System.out.print(s);
return;
}
printPath(predecessor, s, predecessor[d]);
System.out.print(" -> " + d);
}