欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

图论算法——无向图的深度优先搜索和广度优先搜索

程序员文章站 2022-05-21 15:07:52
...

引言

本文介绍了无向图的深度优先搜索和使用广度优先搜索寻找图中的路径,它们分别借助了栈(先进后出)和队列(先进先出)的特性来实现。

有关图的概念可参考博文数据结构之图的概述

深度优先搜索

类似树的深度优先遍历,所谓深度优先即递归的对相邻节点进行访问。从图来看即访问的越来越深,不撞南墙不回头!!

图论算法——无向图的深度优先搜索和广度优先搜索
在访问某个顶点时:

  • 将它标记为已访问
  • 递归地访问它的所有没有标记过的邻接顶点
package com.algorithms.graph;

/**
 * 图的深度优先搜索
 * @author yjw
 * @date 2019/5/16/016
 */
public class DepthFirstSearch {
    /**
     * 标记是否被访问过
     */
    private boolean[] marked;
    private int count;

    public DepthFirstSearch(Graph g,int s) {
        marked = new boolean[g.vertexNum()];
        dfs(g,s);
    }

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

    public int count() {
        return count;
    }

    private void dfs(Graph g,int v) {
        marked[v] = true;
        System.out.print(v + " ");
        count++;
        /**
         * 深度就是递归调用,
         * 访问某个顶点时,将它标记为已访问;
         * 递归地访问它所有没有被标记过的邻接点
         */
        for (int w: g.adj(v)) {
            if (!marked[w]) {
                dfs(g,w);
            }
        }
    }
}

因为存在递归调用,因此底层是利用了栈来实现的。

使用广度优先搜索寻找图中的路径

给定一个起点s,若想寻找从s到某个顶点v的最短路径(所含边数最少),那么就要用到广度优先搜索。

从图来看是优先访问某顶点的相邻顶点,有点像生活中玻璃的裂缝效果。

上面说了深度优先搜索是基于栈的,而广度优先搜索是基于队列的。

图论算法——无向图的深度优先搜索和广度优先搜索
先将起点加入队列,然后重复下列步骤直到队列为空:

  • 取队列中的下一个顶点v并标记它为已访问;
  • 将与v相邻的所有未被标记过的顶点加入队列
package com.algorithms.graph;

import com.algorithms.queue.Queue;
import com.algorithms.stack.Stack;

/**
 * @author yjw
 * @date 2019/5/20/020
 */
public class BreadthFirstPaths {
    private boolean[] marked;
    /**
     * 到达当前顶点的最近顶点
     */
    private int[] edgeTo;
    private final int s;

    public BreadthFirstPaths(Graph g, int s) {
        marked = new boolean[g.vertexNum()];
        edgeTo = new int[g.vertexNum()];
        this.s = s;
        bfs(g, s);
    }

    private void bfs(Graph g, int s) {
        Queue<Integer> queue = new Queue<>();
        marked[s] = true;
        queue.enqueue(s);
        while (!queue.isEmpty()) {
            int v = queue.dequeue();
            /**
             * 将与v相邻的所有未被标记的顶点加入队列
             */
            for (int e: g.adj(v)) {
                if (!marked[e]) {
                    marked[e] = true;
                    edgeTo[e] = v;
                    queue.enqueue(e);
                }
            }
        }
    }

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

    public Iterable<Integer> pathTo(int v) {
        if (!hasPathTo(v)) {
            return null;
        }
        Stack<Integer> path = new Stack<>();
        /**
         * 从路径终点一步步寻找前一个顶点
         */
        for (int x = v; x != s ; x = edgeTo[x]) {
            path.push(x);
        }
        /**
         * 利用栈后进先出刚好可以顺序打印路径上所有顶点
         */
        path.push(s);
        return path;
    }
}

其中QueueStack的实现见 栈和队列的实现