算法_图的深度优先搜索和广度优先搜索

  一.图的基本数据结构  

  图是由一组顶点和一组能够将两个顶点相互连接的边所构成的,一般使用0~V-1这样的数字形式来表示一张含有V个顶点的图.用v-w来指代一张图的边,由于是无向图,因此v-w和w-v是同一种边的两种表示方法.无向图是指边没有方向的图结构在无向图中,边仅仅表示的是两个顶点之间的连接.图的数据结构的可视化如下图所示(其中边上的箭头没有任何意义):

                                                                         算法_图的深度优先搜索和广度优先搜索

  当两个顶点通过一条边相互连接,则称这两个顶点是相邻的.某个顶点的度数即为依附它的边的总数.当两个顶点之间存在一条连接双方的路径的时候,称为这两个顶点是连通的.如果从任意一个顶点都存在一条路径到达另一个任意顶点,则称这幅图是连通图.

  无向图的数据结构中,采用了Bag的数据结构来存储与一个顶点相邻的所有顶点的标识,Bag是一种可以存储然后无序取出的数据结构.无向图的数据结构代码如下:

package graph;

import edu.princeton.cs.algs4.Bag;
import edu.princeton.cs.algs4.In;
/**
 * 无向图的数据结构表示
 * @author Administrator
 *
 */
public class Graph {
    private final int V;    //顶点数目
    private int E;            //边的数目
    private Bag<Integer>[] adj;//邻接表(记录所有与指定的点相邻的点)
    public Graph(int V) {
        this.V=V;
        this.E=0;
        for(int v=0;v<V;v++) {
            adj[v]=new Bag<Integer>();
        }
    }
    //从输入流中获取图(In在algs4.jar中)
    public Graph(In in) {
        this(in.readInt());
        int E=in.readInt();
        for(int i=0;i<E;i++) {
            int v=in.readInt();
            int w=in.readInt();
            addEdge(v,w);
        }
    }
    //顶点个数
    public int V() {
        return V;
    }
    //边的个数
    public int E() {
        return E;
    }
    //添加一条边v-w
    private void addEdge(int v, int w) {
        adj[v].add(w);
        adj[w].add(v);
        E++;
    }
    public Iterable<Integer> adj(int v) {
        return adj[v];
    }
} 

  二.深度优先搜索解决找出无向图中与指定顶点连接的所有顶点的问题

  构造一个类,用于完成找出与一个点所连接的所有点的问题.采用深度优先搜索来完成这个操作:深度优先搜索在访问图的未访问的一个顶点的时候:

  1.将它标记为已访问 

  2.递归的访问它的所有没有被标记过的顶点

  深度优先搜索解决该问题的代码如下:

package graph;
/**
 * 深度优先搜索解决两个点是否在图中连通的问题
 * @author Administrator
 *
 */
public class DepthFirstSearch {
    private boolean[] marked;//一个点是否被访问
    private int count;        //与给定的点相连接的点的总个数
    public DepthFirstSearch(Graph G,int s) {
        marked=new boolean[G.V()];
        dfs(G,s);
    }
    private void dfs(Graph G, int v) {
        marked[v]=true;//标记一个未访问的顶点
        count++;
        //找出所有该点的相邻点,如果没有被访问,则继续遍历
        for(int w:G.adj(v)) {    
            if(!marked[w]) dfs(G,w);
        }
    }
    //点w是否与点s相连通?
    public boolean marked(int w) {
        return marked[w];
    }
    //与s相连通的顶点总数
    public int count() {
        return count;
    }
}

  三.深度优先搜索解决单点路径问题

  单点路径问题:给定一幅图和一个起点s,回答"从s到给定目的顶点v是否存在一条路径,如果有找出这条路径."代码如下,定义了一个edgeTo数组,记录一个第一次被访问的顶点相连接的已被标记的点.例如:通过v深度优先搜索访问的是w的时候,那么利用edgeTo[w]=v.来表示w到v的路径.

/**
 * 深度优先搜索.判断指定点是否与s连通,并给出一条路径
 * @author Administrator
 *
 */
public class DepthFirstPaths {
    private boolean[] marked;//这个顶点上调用过dfs()了吗
    private int []edgeTo;    //从起点到一个顶点的已知路径上的最后一个顶点
    private final int s;    //起点
    public DepthFirstPaths(Graph G,int s) {
        marked=new boolean[G.V()];
        edgeTo=new int[G.V()];
        this.s=s;
        dfs(G,s);
    }
    private void dfs(Graph G, int v) {
        marked[v]=true;
        for(int w:G.adj(v)) {
            if(!marked[w]) {//如果没有被标记,那么在edgeTo[]记录第一次访问它的顶点
                edgeTo[w]=v;//记录下来一个路径
                dfs(G,w);
            }
        }
    }
    //顶点s有没有到指定顶点v的路径
    public boolean hasPathTo(int v) {
        return marked[v];
    }
    //给出一条起点s到v的路径(不保证最短)
    public Iterable<Integer> pathTo(int v) {
        if(!hasPathTo(v)) return null;
        Stack<Integer> stack=new Stack<>();    //需要倒序,采用栈的数据结构
        for(int x=v;x!=s;x=edgeTo[x]) {
            stack.push(x);//将路径中经过的顶点压入栈.
        }
        stack.push(s);
        return stack;
    }
}

  四.广度优先搜索解决无向图的单点最短路径问题

  单点最短路径问题:给出一幅无向图V和顶点s,判断顶点s到指定顶点v是否存在路径?如果有,找出其中最短的一条.可以利用广度优先搜索来解决这个问题.下面的代码使用一个队列保存所有已经被标记过但是其邻接表还没有被检查过的顶点,先将起点加入队列,然后重复下面的步骤直到队列为空:

  取队列中的下一个顶点v并且标记

  将于v相邻的所有未被标记过的点加入队列.

  代码如下:

import java.util.Stack;

import edu.princeton.cs.algs4.Queue;

/**
 * 广度优先搜索.
 * 给定一个点,可以得到其到起点的最短路径(无向图)
 * @author Administrator
 *
 */
public class BreadthFirstPaths {
    private boolean[] marked;//到达该顶点的最短路径是否已知
    private int[] edgeTo;    //到达该顶点的已知路径上的最后一个顶点    
    private final int s;    //起点
    public BreadthFirstPaths(Graph G,int s) {
        this.s=s;
        marked=new boolean[G.V()];
        edgeTo=new int[G.V()];
        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();
            for(int w:G.adj(v)) {
                if(!marked[w]) {
                    edgeTo[w]=v;//记录最短的路径
                    marked[w]=true;
                    queue.enqueue(w);//在没有被标记的情况下,将它放入队列中在之后的循环中操作
                }
            }
        }
    }
    public boolean hasPathTo(int v) {
        return marked[v];
    }
    //给出一条起点s到v的路径(不保证最短)
        public Iterable<Integer> pathTo(int v) {
            if(!hasPathTo(v)) return null;
            Stack<Integer> stack=new Stack<>();    //需要倒序,采用栈的数据结构
            for(int x=v;x!=s;x=edgeTo[x]) {
                stack.push(x);//将路径中经过的顶点压入栈.
            }
            stack.push(s);
            return stack;
    }
}

  采用深度优先搜索和广度优先搜索的图的路径的可视化如下所示(其中红色表示已经被标记,marked[v]=true):

  深度优先搜索的可视化:

                    算法_图的深度优先搜索和广度优先搜索

                     算法_图的深度优先搜索和广度优先搜索

                      算法_图的深度优先搜索和广度优先搜索

                      算法_图的深度优先搜索和广度优先搜索

  广度优先搜索的可视化:

                        算法_图的深度优先搜索和广度优先搜索

                          算法_图的深度优先搜索和广度优先搜索

                       算法_图的深度优先搜索和广度优先搜索

                          算法_图的深度优先搜索和广度优先搜索

  五.连通分量

  可以使用深度优先搜索来寻找一幅图的所有连通分量,可以来判断两个顶点是否存在于一个连通分量中,代码如下所示:

package graph;
/**
 * 利用深度优先搜索处理连通分量的问题
 * @author Administrator
 *
 */
public class CC {
    private boolean[] marked;
    private int[] id;//将顶点和连通分量标识起来的数组
    private int count;//有多少个连通分量
    public CC(Graph G) {
        marked=new boolean[G.V()];
        id=new int[G.V()];
        for(int s=0;s<G.V();s++) {
            //如果有没有被标记的点,说明它与前面的点不在一个分组
            if(!marked[s]) {
                dfs(G,s);
                count++;//连通分量个数+1
            }
        }
    }
    //利用深度优先搜索.将同一个分组的所有的点遍历
    private void dfs(Graph G, int v) {
        marked[v]=true;
        id[v]=count;//在一次搜索中所有的顶点都是属于同一个分组的
        for(int w:G.adj(v)) {
            if(!marked[w]) {
                dfs(G,w);
            }
        }
    }
    //判断顶点v和w是否相连通
    public boolean connected(int v,int w) {
        return id[v]==id[w];
    }
    //获得顶点v的分组
    public int id(int v) {
        return id[v];
    }
    //获取分组的总数
    public int count() {
        return count;
    }
}