图的搜索-BFS和DFS

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/shengqianfeng/article/details/101774708

图的搜索算法

最直接的理解就是在图中的一个顶点出发,到达另一个顶点的路径。图的搜索算法中,广度优先搜索BFS和深度优先搜索DFS就是最简单最粗暴的算法,因为也没什么优化空间,也叫暴力搜索算法
图的存储有两种方式:一种邻接矩阵,一种邻接表。本文主要使用邻接表来存储图。
BFS和DFS都可以用在有向图或者无向图中。本文主要使用的是无向图。
这两种搜索算法都只能适用于图不大的搜索。

广度优先搜索算法

广度优先搜索(Breadth-First-Search),简称BFS,它的搜索策略直观来说就是地毯式层层推进。也就是先查找距离起始顶点最近的,然后是次近的,依次往外扩展搜索。
在这里插入图片描述

代码实现

package com.study.algorithm.graph;

import java.util.LinkedList;
import java.util.Queue;

/**
 * @Auther: JeffSheng
 * @Date: 2019/9/30 13:52
 * @Description: 无向图
 */
public class Graph {

    /**
     * 顶点的个数
     */
    private int v;

    /**
     * 邻接表
     */
    private LinkedList<Integer> adj[];


    public Graph(int v){
        this.v = v;
        adj = new LinkedList[v];
        for (int i = 0; i < v; i++) {
            adj[i] = new LinkedList<>();
        }
    }


    /**
     * 无向图 一条边存两次
     * @param s 起始顶点
     * @param t 终止顶点
     */
    public void addEdge(int s,int t){
        adj[s].add(t);
        adj[t].add(s);
    }


    /**
     * 广度优先遍历
     * @param s
     * @param t
     */
    public void bfs(int s, int t) {
        if (s == t) {
            return;
        }
        boolean[] visited = new boolean[v];
        visited[s]=true;
        Queue<Integer> queue = new LinkedList<>();
        queue.add(s);
        int[] prev = new int[v];
        for (int i = 0; i < v; ++i) {
            prev[i] = -1;
        }
        while (queue.size() != 0) {
           //返回并删除队首元素
            int w = queue.poll();
            for (int i = 0; i < adj[w].size(); ++i) {
                int q = adj[w].get(i);
                if (!visited[q]) {
                    prev[q] = w;
                    if (q == t) {
                        print(prev, s, t);
                        return;
                    }
                    visited[q] = true;
                    queue.add(q);
                }
            }
        }
    }

    private void print(int[] prev, int s, int t) { // 递归打印 s->t 的路径
        if (prev[t] != -1 && t != s) {
            print(prev, s, prev[t]);
        }
        System.out.print(t + " ");
    }
   


    public static void main(String[] args) {
        Graph graph  = new Graph(8);
        graph.addEdge(0,1);
        graph.addEdge(0,3);
        graph.addEdge(1,2);
        graph.addEdge(1,4);
        graph.addEdge(3,4);
        graph.addEdge(2,5);
        graph.addEdge(4,5);
        graph.addEdge(4,6);
        graph.addEdge(5,7);
        graph.addEdge(6,7);
        graph.bfs(0,7);
    }
}

重点解释visited、prev、queue三个变量的含义:
visited:记录已经被访问过的顶点,避免被重复访问,只要顶点q被访问,则visited[q]=true。
queue:queue是一个队列用来存储那些自身已经被访问过,但其连接的顶点还没被访问的顶点。广度优先搜索是逐层访问,也就是说当第K层的所有顶点都被访问过之后,才能访问第K+1层的顶点,
prev:prev数组用来记录搜索路径,当我们从起始顶点s开始,广度优先搜索到顶点t后,prev数组中存储的就是搜索的路径,比如prev[q]=w表示的就是q这个顶点是从w访问过来的。

BFS时间、空间复杂度分析

极端情况下第一个顶点是起始顶点s,最后一个顶点是终止顶点t,那么需要遍历整张图才能找到路径,这个时候每个顶点都要进出一遍队列,每个边也会被访问一次,所以BFS的时间复杂度为O(V+E),V就是顶点个数,E就是边的个数。对于连通图(图中的所有顶点都是连通的)E肯定比V个数要大的多,所以BFS的时间复杂度可以为O(E).
BFS的空间复杂度主要就是由visited数组、prev数组、queue队列三个来决定的,不过他们三个的存储空间大小不会超过顶点个数V,因此空间复杂度为O(V).

深度优先搜索

深度优先搜索(Depth-First-Search),简称DFS。
实际上深度优先搜索使用的思想是回溯思想,回溯思想适合用递归去实现。
实线表示遍历,虚线表示回退
深度优先搜索找到的路径并不是起始顶点s到终止顶点t的最短路径。

代码实现

package com.study.algorithm.graph;

import java.util.LinkedList;
import java.util.Queue;

/**
 * @Auther: JeffSheng
 * @Date: 2019/9/30 13:52
 * @Description: 无向图
 */
public class Graph {

    /**
     * 顶点的个数
     */
    private int v;

    /**
     * 邻接表
     */
    private LinkedList<Integer> adj[];


    public Graph(int v){
        this.v = v;
        adj = new LinkedList[v];
        for (int i = 0; i < v; i++) {
            adj[i] = new LinkedList<>();
        }
    }


    /**
     * 无向图 一条边存两次
     * @param s 起始顶点
     * @param t 终止顶点
     */
    public void addEdge(int s,int t){
        adj[s].add(t);
        adj[t].add(s);
    }




    private void print(int[] prev, int s, int t) { // 递归打印 s->t 的路径
        if (prev[t] != -1 && t != s) {
            print(prev, s, prev[t]);
        }
        System.out.print(t + " ");
    }


    /**
     *  全局变量或者类成员变量
     */
    boolean found = false;

    /**
     * 深度优先遍历
     * @param s 起始顶点
     * @param t 终止顶点
     */
    public void dfs(int s, int t) {
        found = false;
        boolean[] visited = new boolean[v];
        int[] prev = new int[v];
        for (int i = 0; i < v; ++i) {
            prev[i] = -1;
        }
        recurDfs(s, t, visited, prev);
        print(prev, s, t);
    }

    /**
     *
     * @param w
     * @param t
     * @param visited
     * @param prev
     */
    private void recurDfs(int w, int t, boolean[] visited, int[] prev) {
        if (found == true){
            return;
        }
        visited[w] = true;
        if (w == t) {
            found = true;
            return;
        }
        for (int i = 0; i < adj[w].size(); ++i) {
            int q = adj[w].get(i);
            if (!visited[q]) {
                prev[q] = w;
                recurDfs(q, t, visited, prev);
            }
        }
    }


    public static void main(String[] args) {
        Graph graph  = new Graph(8);
        graph.addEdge(0,1);
        graph.addEdge(0,3);
        graph.addEdge(1,2);
        graph.addEdge(1,4);
        graph.addEdge(3,4);
        graph.addEdge(2,5);
        graph.addEdge(4,5);
        graph.addEdge(4,6);
        graph.addEdge(5,7);
        graph.addEdge(6,7);
        graph.bfs(0,7);
    }
}


DFS时间、空间复杂度分析

DFS每条边最多会被访问两次,一次是遍历,一次是回退。所以DFS时间复杂度O(E),
DFS的空间复杂度主要消耗的内存是visited数组、prev数组、递归调用栈,他们都不会超过顶点个数V,所以DFS的空间复杂度为O(V)

猜你喜欢

转载自blog.csdn.net/shengqianfeng/article/details/101774708