首页 > 其他分享 >数据结构第25节 深度优先搜索

数据结构第25节 深度优先搜索

时间:2024-07-14 08:57:27浏览次数:11  
标签:25 优先 adjacencyList graph vertex DFS visited 数据结构 public

深度优先搜索(Depth-First Search,简称 DFS)是一种用于遍历或搜索树或图的算法。DFS 从根节点开始,尽可能深的搜索树的分支,当节点 v 的所在边都己被探寻过,搜索将回溯到上一个节点 w,然后探索 w 的其他未搜索过的子节点。DFS 有两种常用的方式:递归方式和非递归方式(通常使用栈来实现)。

深度优先搜索的基本步骤:

  1. 选择起点:选择一个顶点作为搜索的起点。
  2. 访问顶点:标记这个顶点为已访问,并对它进行必要的操作。
  3. 探索邻接点:选择一个未访问的邻接顶点,然后递归地执行 DFS。
  4. 回溯:当到达叶子节点或所有邻接顶点都已访问时,回溯到上一个调用。
  5. 重复:如果还有未访问的顶点,则选择其中一个作为新的起点,重复上述过程。

Java 中使用递归实现 DFS:

假设我们有一个邻接矩阵 graph 代表无向图,其中 graph[i][j] 为真表示节点 i 和 j 之间存在边。

import java.util.*;

public class DFS {

    private boolean[] visited; // 标记每个顶点是否已被访问

    public DFS(int vertices) {
        visited = new boolean[vertices];
    }

    // 递归实现的 DFS
    public void dfsRecursion(int vertex) {
        visited[vertex] = true;
        System.out.print(vertex + " ");

        for (int i = 0; i < visited.length; i++) {
            if (graph[vertex][i] && !visited[i]) {
                dfsRecursion(i);
            }
        }
    }

    // 非递归实现的 DFS 使用栈
    public void dfsNonRecursion(int startVertex) {
        Stack<Integer> stack = new Stack<>();
        stack.push(startVertex);
        visited[startVertex] = true;

        while (!stack.isEmpty()) {
            int currentVertex = stack.pop();
            System.out.print(currentVertex + " ");

            for (int i = 0; i < visited.length; i++) {
                if (graph[currentVertex][i] && !visited[i]) {
                    stack.push(i);
                    visited[i] = true;
                }
            }
        }
    }

    public static void main(String[] args) {
        int vertices = 4; // 假设有4个顶点
        int[][] graph = {
            {0, 1, 0, 0},
            {1, 0, 1, 1},
            {0, 1, 0, 0},
            {0, 1, 0, 0}
        };

        DFS dfs = new DFS(vertices);
        dfs.dfsRecursion(0); // 从顶点0开始DFS
        System.out.println();

        Arrays.fill(dfs.visited, false); // 重置已访问标记
        dfs.dfsNonRecursion(0); // 从顶点0开始非递归DFS
    }
}

注意事项:

  • 在遍历图时,需要确保不会无限循环,因此需要标记已经访问过的顶点。
  • 当图中存在多个连通分量时,可能需要多次调用 DFS 来遍历整个图。
  • DFS 可以用于解决许多问题,包括但不限于拓扑排序、检测环、路径查找等。

以上是 DFS 在 Java 中的基本实现。你可以根据具体的图数据结构和问题需求进行相应的调整。

当然,我们可以对上面提供的深度优先搜索(DFS)代码进行一些调整和增强,使其更通用和健壮。以下是一些可能的改进:

  1. 参数化图结构:使用泛型来支持不同类型的图结构,例如邻接矩阵或邻接表。
  2. 处理图中的多个连通分量:遍历所有顶点,确保所有顶点都被访问,即使它们不在同一个连通分量中。
  3. 异常处理和边界检查:增加对输入的验证,如检查图是否为空,顶点索引是否有效等。
  4. 可读性和代码风格:改善代码的可读性,例如使用更具描述性的变量名和添加注释。

下面是使用邻接表实现的 DFS 示例,同时处理多个连通分量:

import java.util.*;

public class GraphDFS<T> {

    private Map<T, List<T>> adjacencyList;
    private Set<T> visited;

    public GraphDFS() {
        this.adjacencyList = new HashMap<>();
        this.visited = new HashSet<>();
    }

    // 添加顶点
    public void addVertex(T vertex) {
        adjacencyList.putIfAbsent(vertex, new ArrayList<>());
    }

    // 添加边
    public void addEdge(T src, T dest) {
        if (!adjacencyList.containsKey(src) || !adjacencyList.containsKey(dest)) {
            throw new IllegalArgumentException("Vertex does not exist.");
        }
        adjacencyList.get(src).add(dest);
        // 如果是无向图,也需要反向添加
        adjacencyList.get(dest).add(src);
    }

    // 递归 DFS
    public void dfsRecursive(T vertex) {
        if (!adjacencyList.containsKey(vertex)) {
            throw new IllegalArgumentException("Vertex does not exist.");
        }
        visited.add(vertex);
        System.out.print(vertex + " ");
        for (T neighbor : adjacencyList.get(vertex)) {
            if (!visited.contains(neighbor)) {
                dfsRecursive(neighbor);
            }
        }
    }

    // 非递归 DFS 使用栈
    public void dfsIterative(T startVertex) {
        if (!adjacencyList.containsKey(startVertex)) {
            throw new IllegalArgumentException("Vertex does not exist.");
        }
        Stack<T> stack = new Stack<>();
        stack.push(startVertex);
        visited.add(startVertex);

        while (!stack.isEmpty()) {
            T currentVertex = stack.pop();
            System.out.print(currentVertex + " ");
            for (T neighbor : adjacencyList.get(currentVertex)) {
                if (!visited.contains(neighbor)) {
                    stack.push(neighbor);
                    visited.add(neighbor);
                }
            }
        }
    }

    // 遍历整个图
    public void dfs() {
        for (T vertex : adjacencyList.keySet()) {
            if (!visited.contains(vertex)) {
                dfsRecursive(vertex);
            }
        }
        visited.clear(); // 清除已访问标志,以便再次遍历时重新使用
        for (T vertex : adjacencyList.keySet()) {
            if (!visited.contains(vertex)) {
                dfsIterative(vertex);
            }
        }
    }

    public static void main(String[] args) {
        GraphDFS<String> graph = new GraphDFS<>();

        // 添加顶点
        graph.addVertex("A");
        graph.addVertex("B");
        graph.addVertex("C");
        graph.addVertex("D");

        // 添加边
        graph.addEdge("A", "B");
        graph.addEdge("B", "C");
        graph.addEdge("C", "D");
        graph.addEdge("D", "A");

        // 执行 DFS
        graph.dfs();
    }
}

这个版本的代码提供了更广泛的适用性,可以处理任何类型(泛型 T)的顶点,并且可以处理无向图。同时,它确保了即使图由多个不相连的分量组成,DFS 也会遍历到所有的顶点。

为了使代码更加完善,我们可以添加一些额外的功能和改进,例如:

  1. 分离 DFS 方法:将 DFS 的递归和迭代方法分离成独立的类或方法,以便于测试和复用。
  2. 增加日志记录或调试信息:在关键步骤中添加日志输出,以便于调试和理解程序运行流程。
  3. 异常处理细化:提供更具体的异常信息,帮助用户理解问题所在。
  4. 性能优化:在迭代 DFS 中,优化邻接顶点的查找,避免重复检查是否已访问。

以下是基于上述改进的代码示例:

import java.util.*;

public class Graph<T> {

    private final Map<T, List<T>> adjacencyList;
    private final Set<T> visited;

    public Graph() {
        this.adjacencyList = new HashMap<>();
        this.visited = new HashSet<>();
    }

    public void addVertex(T vertex) {
        adjacencyList.putIfAbsent(vertex, new ArrayList<>());
    }

    public void addEdge(T src, T dest) {
        if (!adjacencyList.containsKey(src) || !adjacencyList.containsKey(dest)) {
            throw new NoSuchElementException("One or both vertices do not exist.");
        }
        adjacencyList.get(src).add(dest);
        adjacencyList.get(dest).add(src); // 无向图
    }

    public void dfs() {
        for (T vertex : adjacencyList.keySet()) {
            if (!visited.contains(vertex)) {
                dfsRecursive(vertex);
            }
        }
        visited.clear();
        for (T vertex : adjacencyList.keySet()) {
            if (!visited.contains(vertex)) {
                dfsIterative(vertex);
            }
        }
    }

    private void dfsRecursive(T vertex) {
        if (!adjacencyList.containsKey(vertex)) {
            throw new NoSuchElementException("Vertex does not exist.");
        }
        visited.add(vertex);
        System.out.print(vertex + " ");
        for (T neighbor : adjacencyList.get(vertex)) {
            if (!visited.contains(neighbor)) {
                dfsRecursive(neighbor);
            }
        }
    }

    private void dfsIterative(T startVertex) {
        if (!adjacencyList.containsKey(startVertex)) {
            throw new NoSuchElementException("Vertex does not exist.");
        }
        Stack<T> stack = new Stack<>();
        stack.push(startVertex);
        visited.add(startVertex);

        while (!stack.isEmpty()) {
            T currentVertex = stack.pop();
            System.out.print(currentVertex + " ");
            List<T> neighbors = adjacencyList.get(currentVertex);
            for (int i = neighbors.size() - 1; i >= 0; i--) { // Reverse iteration to avoid re-checking visited flag
                T neighbor = neighbors.get(i);
                if (!visited.contains(neighbor)) {
                    stack.push(neighbor);
                    visited.add(neighbor);
                }
            }
        }
    }
}

// Test code
public class Main {
    public static void main(String[] args) {
        Graph<String> graph = new Graph<>();

        // Adding vertices
        graph.addVertex("A");
        graph.addVertex("B");
        graph.addVertex("C");
        graph.addVertex("D");
        graph.addVertex("E");

        // Adding edges
        graph.addEdge("A", "B");
        graph.addEdge("B", "C");
        graph.addEdge("C", "D");
        graph.addEdge("D", "A");
        graph.addEdge("E", "C"); // Separate component

        // Perform DFS
        graph.dfs();
    }
}

在这个版本中,DFS 的递归和迭代方法被封装在了 Graph 类的私有方法中,这使得主类更加清晰。同时,我们在迭代 DFS 中采用了逆向迭代的方法来避免重复检查邻接顶点的访问状态,这样可以稍微提高效率,特别是在邻接列表很长的情况下。此外,我们增加了异常处理的细节,使得错误信息更加明确。

标签:25,优先,adjacencyList,graph,vertex,DFS,visited,数据结构,public
From: https://blog.csdn.net/hummhumm/article/details/140400733

相关文章

  • 昇思25天学习打卡营第20天|K近邻算法实现红酒聚类
    这节课主要学习使用MindSpore在部分wine数据集上进行KNN实验。目标是了解KNN的基本概念以及如何使用MindSpore进行KNN实验。1.K近邻算法原理介绍1.1K近邻算法(K-Nearest-Neighbor,KNN)是一种用于分类和回归的非参数统计方法,最初由Cover和Hart于1968年提出(Cover等人,196......
  • 【数据结构与算法】详解二叉树下:实践篇————通过链式结构深入理解并实现二叉树
          ......
  • 深度优先搜索+算法设计+python
    一、问题描述小明想知道哪个岛是最大的岛屿,请你用深度优先遍历算法来帮助他。如图所示,为了方便计算,我们使用一个二维数组来表示一片海域,用0表示水面,用1表示陆地,我们的任务是找出其中最大的岛屿。注意,岛屿是指上下左右四个方向相连接的陆地区域。二、问题求解deflargest_is......
  • 最小数字游戏(Lc2974)——模拟+优先队列(小根堆)、排序+交换
    你有一个下标从 0 开始、长度为 偶数 的整数数组 nums ,同时还有一个空数组 arr 。Alice和Bob决定玩一个游戏,游戏中每一轮Alice和Bob都会各自执行一次操作。游戏规则如下:每一轮,Alice先从 nums 中移除一个 最小 元素,然后Bob执行同样的操作。接着,Bob会将......
  • 数据结构题目:几种典型排序算法的实现
    1、实验目的实现几种典型的排序算法2、实验具体要求分别利用直接插入/折半插入/希尔/冒泡/快速/简单选择排序算法实现将待排序序列{26,6,18,8,36,66,56,76,99}由小到大排序,并输出结果。3、实验设计思路(编程语言、模块划分及函数功能描述等)模块划分及函数功能描述:主函数模......
  • 数据结构,(动态)顺序表,C语言实现
    ——如果代码存在问题,请务必评论告诉我,感激不尽(#^.^#)——动态和静态的顺序表差别主要在于开辟内存的方式,动态顺序表中的数据所在内存是通过malloc函数实现的,这也意味着,动态顺序表可以更改存储数据的内存大小,其他的话基本没什么差别1.数据类型定义 structElemType想要建......
  • 数据结构与算法学习day4之单向链表
    1.单向链表的的定义链表是有序的列表,这是它在内存中的存储,如下图所示:链表是以节点的形式存储的,是链式存储每个节点都包含两个部分,一个是data域,一个是next域指向下一个节点每个节点不一定是连续存储链表分为带头节点和不带头节点2.单向链表的实现思路(1)添加添加节点的......
  • 数据结构(队列的实现)
    目录队列队列的定义队列的实现创建队列的结构队列的初始化进行插入数据删除数据计算个数 判断是否为空返回队列头部数据返回队列尾部数据队列队列的定义队列是:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First......
  • OpenAI首席技术官:没将产品优先程度置于安全之前,超级对齐团队解散不影响 | 最新快讯
    “我们看到了科学将引领我们走向何方,所以也理解人们对现在即将发生的事感到恐惧和焦虑。”近日,OpenAI首席技术官米拉·穆拉蒂(MiraMurati)在一次访谈中再次谈起AI(人工智能)安全,并对“超级对齐”安全团队解散、ChatGPT斯嘉丽语音风波、公司面临监管审查等问题作出回应。......
  • 运算符的关系,什么叫一元运算符,二元运算符,三元运算符,运算符优先级,以及运算符的
    按照操作数个数区分:一元运算符:一元运算符只需要一个操作数。常见的一元运算符有:1.递增和递减运算符:++和--,用于对操作数进行增加或减少1。2.正负号运算符:+和-,用于表示正负数。3.逻辑非运算符:!,用于对布尔值进行取反。二元运算符:二元运算符需要两个操作数。常见的二元运......