图的深度优先遍历算法

简介:

前言

图的遍历与前面文章中的二叉树遍历还是存在很大区别的。所谓图的遍历指的是从图中的某一个顶点出发访问图中的其余顶点,并且需要保证每个顶点只被访问一次。由于图比二叉树复杂得多,所以前面二叉树的遍历算法在图中是行不通的。因为对于任意一个顶点来讲,都可能与其余的顶点发生连接。如果不对访问的顶点做一些处理,出发重复访问的几率是很高的。因此,一个基本思想是设置一个标记数组,主要用于标记已经被访问过的顶点。图的遍历算法主要有两种:深度优先遍历和广度优先遍历。本篇文章主要介绍的是深度优先遍历算法。

深度优先遍历的具体过程

深度优先遍历,简称DFS。具体思想是不放过任何一个死角。在图的遍历中就是从图的某个顶点v出发,访问此顶点,然后从v的未被访问过的邻接点出发深度优先遍历图,直至图中的所有和v有路径相通的顶点都被访问到(对于连通图来讲)

为了更好说明深度优先遍历的过程,以下面的图为例:

图

上图的邻接表定义如下:

邻接表

注意:顶点0的第一个元素是2而不是5,其顶点类似。

  1. 起点是顶点0,后面的遍历过程从顶点0开始,把顶点0标记为已访问
  2. 因为顶点2是顶点0的邻接表的第一个元素,所以下一次递归从顶点2开始,同时把顶点2标记为已访问
  3. 顶点2的递归遍历开始,由于顶点2的邻接表的第一个元素是0,但是0已经被访问过了,所以访问顶点1,1没有被访问,于是将1标记为已访问,递归继续从顶点1开始
  4. 查找上表中顶点1的第一个元素,是顶点0,由于已经被访问过,所以访问顶点2,2也被访问过了,于是从顶点1的递归遍历结束,返回到顶点2继续递归。
  5. 查找顶点2的下一个元素,顶点3,没有被访问,于是将顶点3标记为已访问,递归于是从顶点3开始
  6. 查找顶点3邻接表的第一个元素,是顶点5,没有被访问,于是将顶点5标记为已访问,递归从顶点5开始
  7. 顶点5从其邻接表查找第一个元素,是顶点3,已被访问过,继续查找顶点0,也被访问,于是递归从5结束,返回到顶点3继续递归
  8. 查找顶点3邻接表的下一个元素,是顶点4,没有被访问过,于是将顶点4标记为已访问,递归从顶点4继续开始
  9. 顶点4查找其邻接表的第一个元素,发现顶点3已被访问过,继续查找其下一个元素,发现顶点2也被访问过,于是递归从顶点4结束,返回到顶点3继续递归
  10. 顶点查找下一个元素是顶点2了,也是顶点3邻接表的最后一个元素,发现顶点2已经被访问过了,所以递归从顶点3结束,返回到顶点2继续递归
  11. 顶点查找其邻接表的下一个元素,是顶点4,也是其邻接表最后一个元素,发现顶点已被访问过,所以递归从顶点2结束,返回到顶点0继续递归
  12. 顶点0继续查找其邻接表的下一个元素,发现顶点1余顶点5都被访问过了,所以递归结束。总的遍历结束。

从以上过程来看,上图的顶点访问次序依次是:0,2,1,3,5,4。

深度优先遍历算法的实现

首先需要定义一个存储图的数据结构,在Java中可以使用邻接表来实现图存储。具体而言就是,图的顶点用一维数组存储,每个顶点的邻接顶点用一个单链表进行存储。

图的数据结构:邻接表的实现

package com.rhwayfun.algorithm.graph;

public class MyGraph {

    //顶点数目
    private int V;
    //边的数目
    private int E;
    //邻接表
    private Bag<Integer>[] adj;

    public MyGraph(int V){
        this.V = V;
        this.E = 0;
        //创建邻接表
        adj = (Bag<Integer>[])new Bag[V];
        //将所有链表初始化
        for(int v = 0; v < V; v++){
            adj[v] = new Bag<Integer>();
        }
    }

    public int V(){
        return V;
    }

    public int E(){
        return E;
    }

    public void addEdge(int v,int w){
        //将w添加到v的链表中
        adj[v].add(w);
        //将v添加到w的链表中
        adj[w].add(v);
        E++;
    }
    //获取顶点v的邻接表顶点列表
    public Iterable<Integer> adj(int v){
        return adj[v];
    }
}

深度优先遍历算法的实现代码:

package com.rhwayfun.algorithm.graph;

/**
 * 深度优先搜索
 * <p>Title:DepthFirstSearch</p>
 * <p>Description:</p>
 * @author rhwayfun
 * @date Dec 22, 2015 8:04:23 PM
 * @version 1.0
 */
public class DepthFirstSearch {

    //创建一个标记数组
    private boolean[] marked;
    //访问计数器
    private int count;

    /**
     * 构造一幅图并进行深度优先遍历
     * <p>Description: </p>
     * @param G 读入的图
     * @param s 开始遍历的顶点
     */
    public DepthFirstSearch(MyGraph G,int s) {
        marked = new boolean[G.V()];
        dfs(G,s);
    }

    private void dfs(MyGraph G, int s) {
        marked[s] = true;
        count++;
        System.out.print(s + " ");
        for(int w : G.adj(s)){
            //如果没有被访问过就继续遍历
            if(!marked[w]) dfs(G, w);
        }
    }

    public boolean[] getMarked() {
        return marked;
    }

    public int getCount() {
        return count;
    }


}

深度优先遍历算法的非递归实现方式

使用非递归的方式与递归的思想是一致的,不同的在于需要使用一个栈保存遍历的顶点。下面是具体的实现代码:

package com.rhwayfun.algorithm.graph;

import java.util.Iterator;
import java.util.Stack;

/**
 * 使用非递归方式对图进行深度优先遍历
 * <p>Title:NonrecursiveDFS</p>
 * <p>Description:</p>
 * @author rhwayfun
 * @date Dec 22, 2015 10:43:35 PM
 * @version 1.0
 */
public class NonrecursiveDFS {

    //创建一个标记数组标记访问过的元素
    private boolean[] marked;

    @SuppressWarnings("unchecked")
    public NonrecursiveDFS(MyGraph G, int s){
        marked = new boolean[G.V()];
        //创建一个迭代器迭代每个顶点的邻接表
        Iterator<Integer>[] adj = (Iterator<Integer>[])new Iterator[G.V()];
        //获得每个顶点的邻接表
        for(int v = 0; v < G.V(); v++){
            adj[v] = G.adj(v).iterator();
        }

        //创建一个栈用户存放遍历的顶点
        Stack<Integer> stack = new Stack<Integer>();
        marked[s] = true;
        System.out.print(s + " ");
        stack.add(s);
        while(!stack.isEmpty()){
            int v = stack.peek();
            //如果有邻接表的话,就继续遍历
            if(adj[v].hasNext()){
                int w = adj[v].next();
                //判断是否已被访问过
                if(!marked[w]){
                    //没访问过就将器标记为已访问过,下次不会再访问了
                    marked[w] = true;
                    System.out.print(w + " ");
                    stack.push(w);
                }
            }else{
                //如果没有邻接表的话就将该顶点弹出栈顶
                stack.pop();
            }
        }
    }

    public boolean marked(int v) {
        return marked[v];
    }


}
目录
相关文章
|
5月前
|
存储 算法 Java
Java中,树与图的算法涉及二叉树的前序、中序、后序遍历以及DFS和BFS搜索。
【6月更文挑战第21天】Java中,树与图的算法涉及二叉树的前序、中序、后序遍历以及DFS和BFS搜索。二叉树遍历通过访问根、左、右子节点实现。DFS采用递归遍历图的节点,而BFS利用队列按层次访问。以下是简化的代码片段:[Java代码略]
44 4
|
20天前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
16 0
|
2月前
|
算法 调度
作业调度算法_先来先服务算法_短作业优先算法_高响应比优先算法
本文介绍了作业调度算法,包括先来先服务(FCFS)、短进程优先(SJF)和高响应比优先(HRRN)算法。通过分析进程的到达时间和所需CPU服务时间,计算进程的开始时间、完成时间、平均周转时间和平均带权周转时间,以评估不同算法的性能。FCFS适合长作业,SJF适合短作业,而HRRN则综合了两者的优点。
71 12
|
3月前
|
存储 算法 Java
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
75 0
|
4月前
|
存储 算法 Python
“解锁Python高级数据结构新姿势:图的表示与遍历,让你的算法思维跃升新高度
【7月更文挑战第13天】Python中的图数据结构用于表示复杂关系,通过节点和边连接。常见的表示方法是邻接矩阵(适合稠密图)和邻接表(适合稀疏图)。图遍历包括DFS(深度优先搜索)和BFS(广度优先搜索):DFS深入探索分支,BFS逐层访问邻居。掌握这些技巧对优化算法和解决实际问题至关重要。**
36 1
|
4月前
|
算法 JavaScript
JS 【详解】树的遍历(含深度优先遍历和广度优先遍历的算法实现)
JS 【详解】树的遍历(含深度优先遍历和广度优先遍历的算法实现)
65 0
JS 【详解】树的遍历(含深度优先遍历和广度优先遍历的算法实现)
|
4月前
|
存储 算法 搜索推荐
|
4月前
|
数据采集 存储 算法
「AIGC算法」图搜索算法详解
本文探讨了图搜索算法,包括遍历和最短路径搜索。DFS和BFS是遍历算法,前者使用栈深入搜索,后者用队列逐层遍历。Dijkstra、Bellman-Ford、A*、Floyd-Warshall和Johnson算法则解决最短路径问题。文中还给出了DFS的Python实现示例。这些算法在路径规划、网络分析等领域有重要应用。
65 0
|
4月前
|
算法 JavaScript
JS 【详解】二叉树(含二叉树的前、中、后序遍历技巧和算法实现)
JS 【详解】二叉树(含二叉树的前、中、后序遍历技巧和算法实现)
46 0