利用广度优先遍历(BFS)计算最短路径——Java

今天看了数据结构中的图论,其中讲到求无向无权图的最短路径的方法,就是使用了广度优先遍历的方法进行求解。对照算法在Intellij IDEA上自己实现了一遍,下面把程序贴出来并进行一些解释。

首先我们要构建一个无向无权图,我要构建成如下所示的图。

利用广度优先遍历(BFS)计算最短路径——Java_第1张图片

然后我想计算出从North Gate到Canteen的最短路径,程序的输出结果应该为:North Gate, Square, Canteen

我在实现的过程中使用了策略模式,首先我定义了一个算法接口Algorithm:

public interface Algorithm {
    /**
     * 执行算法
     */
    void perform(Graph g, String sourceVertex);
    /**
     * 得到路径
     */
    Map getPath();
}

然后定义了图:

public class Graph {
    // 图的起点
    private String firstVertex;
    // 邻接表
    private Map> adj = new HashMap<>();
    // 遍历算法
    private Algorithm algorithm;
    public Graph(Algorithm algorithm) { //策略模式,将Graph与算法分离
        this.algorithm = algorithm;
    }

    public void setFirstVertex(String firstVertex) {
        this.firstVertex = firstVertex;
    }

    /**
     * 执行算法
     */
    public void done() {
        algorithm.perform(this, firstVertex);
    }
    /**
     * 得到从起点到{@code vertex}点的最短路径
     * @param vertex
     * @return
     */
    public Stack findPathTo(String vertex) {
        Stack stack = new Stack<>();
        stack.add(vertex);
        Map path = algorithm.getPath();
        for (String location = path.get(vertex) ; !location.equals(firstVertex) ; location = path.get(location)) {
            stack.push(location);
        }
        stack.push(firstVertex);
        return stack;
    }
    /**
     * 添加一条边
     */
    public void addEdge(String fromVertex, String toVertex) {
        if (firstVertex == null) {
            firstVertex = fromVertex;
        }
        adj.get(fromVertex).add(toVertex);
        adj.get(toVertex).add(fromVertex);
    }
    /**
     * 添加一个顶点
     */
    public void addVertex(String vertex) {
        adj.put(vertex, new ArrayList());
    }

    public Map> getAdj() {
        return adj;
    }
}

策略模式在这里体现,我们把Graph类与Algorithm分离,在构造GraphGraph(Algorithm algorithm)的过程中可以传入一个Algorithm的实现选择遍历算法,我们这里只进行了BFS的实现,类似的可以使用其他算法来实现Algorithm接口。

无向图的存储结构为邻接表,这里用一个Map表示邻接表,map的key是学校地点(String),value是一个与该地点相连通的地点表(List)。

然后,到了最核心的算法部分,编写Algorithm的BFS实现:

public class BroadFristSearchAlgorithm implements Algorithm{
    // 保存已经访问过的地点
    private List visitedVertex;
    // 保存最短路径
    private Map path;

    @Override
    public void perform(Graph g, String sourceVertex) {
        if (null == visitedVertex) {
            visitedVertex = new ArrayList<>();
        }
        if (null == path) {
            path = new HashMap<>();
        }
        BFS(g, sourceVertex);
    }

    @Override
    public Map getPath() {
        return path;
    }

    private void BFS(Graph g, String sourceVertex){
        Queue queue = new LinkedList<>();
        //标记起点
        visitedVertex.add(sourceVertex);
        // 起点入列
        queue.add(sourceVertex);
        while(!queue.isEmpty()){
            String ver = queue.poll();  //返回队列头部,或null,如果队列为空
            List toBeVisitedVertex = g.getAdj().get(ver);
            for (String v : toBeVisitedVertex) {
                if (!visitedVertex.contains(v)) {
                    visitedVertex.add(v);
                    path.put(v, ver);
                    queue.add(v);   //后添加的要等前面的所有距离为currDist的顶点都被处理之后才被处理
                }
            }
        }
    }
}

其中,path是Map类型,意为从value到key的一条路径。

BFS的实现遵循如下规则:

  1. 将起点标记为已访问并放入队列;
  2. 从队列中取出一个顶点,得到与该顶点相通的所有顶点;
  3. 遍历这些顶点,先判断顶点是否已被访问过,如果否,标记该点为已访问,记录当前路径,并将当前顶点入列;
  4. 重复2、3,直到队列为空。

最后,我编写了测试用例来验证程序的正确性。

public class BFSTest {

    @Before
    public void before() throws Exception {
    }

    @After
    public void after() throws Exception {
    }

    /**
    *
    * Method: test()
    *
    */
    @Test
    public void BFSTest() throws Exception {
    //TODO: Test goes here...
        Graph g = new Graph(new BroadFristSearchAlgorithm());
        //添加顶点
        g.addVertex("North Gate");
        g.addVertex("South Gate");
        g.addVertex("Classroom");
        g.addVertex("Square");
        g.addVertex("Toilet");
        g.addVertex("Canteen");

        //添加边
        g.addEdge("North Gate", "Classroom");
        g.addEdge("North Gate", "Square");
        g.addEdge("Classroom", "Toilet");
        g.addEdge("Square", "Toilet");
        g.addEdge("Square", "Canteen");
        g.addEdge("Toilet", "South Gate");
        g.addEdge("Toilet", "South Gate");

        g.done();
        g.setFirstVertex("North Gate");
        Stack result = g.findPathTo("Canteen");
        System.out.println("BFS: From [North Gate] to [Canteen]:");
        while (!result.isEmpty()) {
            System.out.println(result.pop());
        }
    }
} 

运行测试用例,得到以下结果:

BFS: From [North Gate] to [Canteen]:
North Gate
Square
Canteen

在Intellij IDEA中创建测试用例的方法首先要导入junit单元测试的包,现在一般是使用junit4。首先需要进入Preferences中打开Plugins,在里面下载JunitGenerator V2.0以及Junit插件,然后我们就可以在类文件中选择Code->Generate创建Junit Test。一般测试用例类会放在与src文件夹统一目录下的Test文件夹下,而且Test文件夹要右键设置为Mark Directory as->Test Sources Root,如下图所示:

利用广度优先遍历(BFS)计算最短路径——Java_第2张图片

你可能感兴趣的:(算法)