算法#17--无向图的深度优先搜索和广度优先搜索

无向图

1.定义

在数学上,一个图(Graph)是表示物件与物件之间的关系的方法,是图论的基本研究对象。一个图看起来是由一些小圆点(称为顶点或结点)和连结这些圆点的直线或曲线(称为边)组成的。

如果给图的每条边规定一个方向,那么得到的图称为有向图,其边也称为有向边。在有向图中,与一个节点相关联的边有出边和入边之分,而与一个有向边关联的两个点也有始点和终点之分。相反,边没有方向的图称为无向图

某个顶点的度数即为连接它的边的总数。路径或者环的长度为其中所包含的边数。

2.代码

我们可以抽象出以下的表示图的API:

Graph的API的实现可以由多种不同的数据结构来表示,最基本的是维护一系列边的集合,如下:

还可以使用邻接矩阵来表示:

也可以使用邻接列表来表示:

由于采用如上方式具有比较好的灵活性,采用邻接列表来表示的话,可以定义如下数据结构来表示一个Graph对象。

点击下载代码


import java.util.ArrayList;
import java.util.List;

public class Graph {
    private int verticals;//顶点个数
    private int edges;//边的个数
    private List<Integer>[] adjacency;//顶点联接列表

    @SuppressWarnings("unchecked")
    public Graph(int vertical) {
        this.verticals = vertical;
        this.edges = 0;
        adjacency = (List<Integer>[])new List[vertical]; 
        for (int v = 0; v < vertical; v++) {
            adjacency[v]=new ArrayList<Integer>();
        }
    }

    public int GetVerticals () {
        return verticals;
    }

    public int GetEdges() {
        return edges;
    }

    public void AddEdge(int verticalStart, int verticalEnd) {
        adjacency[verticalStart].add(verticalEnd);
        adjacency[verticalEnd].add(verticalStart);
        edges++;
    }

    public List<Integer> GetAdjacency(int vetical) {
        return adjacency[vetical];
    }

    public static void main(String[] args) {
        Graph G = new Graph(13);
        G.AddEdge(0, 5);
        G.AddEdge(4, 3);
        G.AddEdge(0, 1);
        G.AddEdge(9, 12);
        G.AddEdge(6, 4);
        G.AddEdge(5, 4);
        G.AddEdge(0, 2);
        G.AddEdge(11, 12);
        G.AddEdge(9, 10);
        G.AddEdge(0, 6);
        G.AddEdge(7, 8);
        G.AddEdge(9, 11);
        G.AddEdge(5, 3);
        G.AddEdge(4, 9);

        List<Integer> list = G.GetAdjacency(4);
        for(int w : list) {
            System.out.print(w + " ");
        }
    }
}

输出:

3 6 5 9 

采用以上三种表示方式的效率如下:

深度优先搜索

1.原理

在谈论深度优先算法之前,我们可以先看看迷宫探索问题。下面是一个迷宫和图之间的对应关系: 迷宫中的每一个交会点代表图中的一个顶点,每一条通道对应一个边。

迷宫探索可以采用Trémaux绳索探索法。即:

  • 选择一条没有标记过的通道,在你走过的路上铺一条绳子;
  • 标记所有你第一次路过的路口和通道;
  • 当来到一个标记过的路口时(用绳子)回退到上个路口;
  • 当会退到的路口已经没有可走的通道时继续回退。

图示如下:

下面是迷宫探索的一个小动画:

接下来我们看图的搜索方法。

2.代码

定义一个edgesTo变量来后向记录所有到s的顶点的记录,和仅记录从当前节点到起始节点不同,我们记录图中的每一个节点到开始节点的路径。为了完成这一日任务,通过设置edgesTo[w]=v,我们记录从v到w的边,换句话说,v-w是最后一条从s到达w的边。 edgesTo[]其实是一个指向其父节点的树。

点击下载代码


public class DepthFirstSearch {
    private boolean[] marked;//记录是否被dfs访问过
    private int[] edgesTo;//记录最后一个到当前节点的顶点
    private int s;//搜索的起始点

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

    private void dfs(Graph g, int v) {
        marked[v] = true;
        for(Integer w : g.GetAdjacency(v)) {
            if (!marked[w]) {
                edgesTo[w] = v;
                dfs(g,w);
            }
        }
    }

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

    public Stack<Integer> PathTo(int v) {

        if (!HasPathTo(v)) {
            return null;
        }
        Stack<Integer> path = new Stack<Integer>();

        for (int x = v; x!=s; x=edgesTo[x]) {
            path.push(x);
        }
        path.push(s);
        return path;
    }

    public static void main(String[] args) {
        Graph G = new Graph(13);
        G.AddEdge(0, 5);
        G.AddEdge(4, 3);
        G.AddEdge(0, 1);
        G.AddEdge(9, 12);
        G.AddEdge(6, 4);
        G.AddEdge(5, 4);
        G.AddEdge(0, 2);
        G.AddEdge(11, 12);
        G.AddEdge(9, 10);
        G.AddEdge(0, 6);
        G.AddEdge(7, 8);
        G.AddEdge(9, 11);
        G.AddEdge(5, 3);
        G.AddEdge(4, 9);

        DepthFirstSearch dfs = new DepthFirstSearch(G, 0);
        Stack<Integer> path = dfs.PathTo(11);
        while(path.size() != 0) {
            System.out.print(path.pop() + " ");
        }       
    }
}

输出:

0 5 4 9 12 11 

广度优先搜索

1.原理

我们很自然地还经常对下面这些问题敢兴趣。

单点最短路径。给定一幅图和一个起点s,回答“从s到给定目的顶点v是否存在一条路径?如果有,找出其中最短的那条等类似问题。”

解决这个问题的经典方法叫做广度优先搜索。

其主要原理是:

  • 将 s放到FIFO中,并且将s标记为已访问
  • 重复直到队列为空
  • 移除最近最近添加的顶点v
  • 将v未被访问的节点添加到队列中
  • 标记他们为已经访问

广度优先是以距离递增的方式来搜索路径的。

点击下载代码


public class BreadthFirstSearch {
    private boolean[] marked;
    private int[] edgeTo;
    private int s;//搜索的起始点

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

    private void bfs(Graph g, int s) {
        Queue<Integer> queue = new Queue<Integer>();
        marked[s] = true;
        queue.enqueue(s);
        while (queue.size()!=0) {
            int v = queue.dequeue();
            for(int w : g.GetAdjacency(v)) {
                if (!marked[w]) {
                    edgeTo[w] = v;
                    marked[w] = true;
                    queue.enqueue(w);
                }
            }
        }
    }

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

    public Stack<Integer> PathTo(int v) {
        if (!HasPathTo(v)) {
            return null;
        }

        Stack<Integer> path = new Stack<Integer>();
        for (int x = v; x!=s; x=edgeTo[x]) {
            path.push(x);
        }
        path.push(s);
        return path;
    } 

    public static void main(String[] args) {
        Graph G = new Graph(13);
        G.AddEdge(0, 5);
        G.AddEdge(4, 3);
        G.AddEdge(0, 1);
        G.AddEdge(9, 12);
        G.AddEdge(6, 4);
        G.AddEdge(5, 4);
        G.AddEdge(0, 2);
        G.AddEdge(11, 12);
        G.AddEdge(9, 10);
        G.AddEdge(0, 6);
        G.AddEdge(7, 8);
        G.AddEdge(9, 11);
        G.AddEdge(5, 3);
        G.AddEdge(4, 9);

        BreadthFirstSearch bfs = new BreadthFirstSearch(G, 0);
        Stack<Integer> path = bfs.PathTo(3);
        while(path.size() != 0) {
            System.out.print(path.pop() + " ");
        }       
    }
}

输出:

0 5 4 9 11  (最短路径)

总结

本文简要介绍了无向图中的深度优先和广度优先算法,这两种算法时图处理算法中的最基础算法,也是后续更复杂算法的基础。它们搜索路径如下:

附:关于stackqueue代码下载

发布了45 篇原创文章 · 获赞 21 · 访问量 12万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章