常见搜索算法及Java实现

目录

一、使用场景对比

二、常用搜索算法

2.1 线性搜索

2.2 二分搜索

2.3 DFS:深度优先搜索

2.3.1 二叉树的遍历(前/中/后序遍历)

2.3.2 图的遍历(邻接表):递归

2.3.3 图的遍历:迭代(栈)

2.3.4 图:查找路线

2.3.5 检测图中的环

2.3.6 图:拓扑排序

2.4 BFS:广度优先搜索

2.4.1 二叉树的遍历(层序遍历)

2.4.2 图的遍历

2.4.3 图的层次遍历

2.4.4 图:寻找最短路径


一、使用场景对比

算法核心思想时间复杂度空间复杂度优点缺点典型使用场景
线性搜索逐个遍历,暴力检查每个元素。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对比

特性DFSBFS
数据结构队列
内存使用较少较多
找到的路径不一定最短最短路径
适用场景拓扑排序、连通性检测最短路径、层级遍历

二、常用搜索算法

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:深度优先搜索

核心思想:尽可能深地探索树的分支,直到到达叶子节点,然后回溯。是一种用于遍历或搜索树或图的算法。

  1. 从根节点(或任意节点)开始。

  2. 访问该节点,并将其标记为已访问。

  3. 递归地访问该节点的每一个未访问过的相邻节点。

时间复杂度: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) 来实现。

  1. 将根节点放入队列。

  2. 从队列中取出第一个节点并访问它。

  3. 将这个节点的所有未访问过的相邻节点(例如,二叉树的左右子节点)加入队列。

  4. 重复步骤 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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熙客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值