用广度优先搜索法解决迷宫问题

322 阅读2分钟

用广度优先搜索解决迷宫问题

我们如何才能找到从A点到B点的最快方法?解决这样的问题在计算中非常常见。视频游戏中的敌方AI需要能够找到通往玩家的最快途径。谷歌地图需要找到到达目的地的最快方法。在这里,我们只是想解决一个迷宫。

几种 寻路 算法。我们今天要关注的是广度优先搜索或BFS。这种算法可以保证在未加权的图上给出最快的路径。

什么是图?

Picture of a graph. The black circles are vertices and the red lines are edges.

这是个图。与树不同,图被允许有循环引用。图中的每一个点都被称为一个顶点。连接顶点的线被称为

有些图可能是加权的,这意味着有些边比其他边长。有些边可能是有方向的,比如说,连接只能朝一个方向走。

A graph with weights and directed edges

我们将专注于非加权图,因为BFS在其他方面不是很有用。BFS可以在有向图上工作,但如何做到这一点,将留给读者一个练习。

创建一个哈希图

我们需要能够在计算机的内存中存储图形。每个顶点可以简单地表示为其邻居的列表。

// in case you can't tell, these examples are using Java
class Vertex {
    private LinkedList<Vertex> adjacents;

    public Vertex() {
        this.adjacents = new LinkedList<>();
    }

    public void addAdjacent(Vertex adjacentVertex) {
        adjacents.add(adjacentVertex);
    }

    public LinkedList<Vertex> getAdjacents() {
        return this.adjacents;
    }

    // you probably want to override the hash() function
}

图可以是一个HashMap,包含每个顶点的名称和顶点。如果你不知道什么是HashMap,你可能想看一下这篇文章

class Graph {
    private HashMap<String, Vertex> vertices;

    public Graph() {
        this.vertices = new HashMap<>();
    }

    public Vertex addVertex(String name) {
        Vertex vertex = new Vertex();
        vertices.put(name, vertex);
        return vertex;
    }

    public void addEdge(String vertex1, String vertex2) {
        Vertex v1 = vertices.get(vertex1);
        Vertex v2 = vertices.get(vertex2);

        v1.addAdjacent(v2);
        v2.addAdjacent(v1);
    }
}

遍历图

我们需要一个要遍历的顶点队列。每当我们到达某个顶点时,我们就将其所有相邻的顶点添加到队列中,除非该顶点已经被到达。我们需要一个HashSet来存储我们已经到达的顶点。我们将制作一个breadthFirstTraversal 方法,其中包含以下内容。

public void breadthFirstTraversal(String start, String end) {
    Vertex startVert = vertices.get(start);
    Vertex endVert = vertices.get(end);

    LinkedList<Vertex> queue = new LinkedList<>(); // LinkedList implements Queue
    HashSet<Vertex> visited = new HashSet<>();

    visited.add(startVert); // this is the first vertex

    Vertex current = startVert; // the current vertex to check
    while (current != endVert) { // repeats until the end is reached

        LinkedList<Vertex> adjacents = current.getAdjacents(); // get adjacents

        for (Vertex v: adjacents) { // add all the adjacents
            if (!visited.contains(v)) { // but only if it hasn't already been traversed
                visited.add(v);
                queue.add(v);
            }
        }

        current = queue.remove(); // goes to the next vertex
    }
}

创建一个路径

当然,这个算法所做的就是遍历图形。我们需要一个路径。幸运的是,这出乎意料的简单。对于每个顶点,我们都需要存储前一个顶点。我们可以把HashSet改成HashMap来做这件事。我们需要一个路径的顶点列表。我们将从添加最后一个顶点的前一个顶点开始,然后是它的前一个顶点,以此类推,直到我们到达起点。

public LinkedList<Vertex> breadthFirstSearch(String start, String end) {
    Vertex startVert = vertices.get(start);
    Vertex endVert = vertices.get(end);

    LinkedList<Vertex> queue = new LinkedList<>(); // LinkedList implements Queue
    HashMap<Vertex, Vertex> visited = new HashMap<>();

    visited.put(startVert, null); // this is the first vertex

    Vertex current = startVert; // the current vertex to check
    while (current != endVert) { // repeats until the end is reached

        LinkedList<Vertex> adjacents = current.getAdjacents(); // get adjacents

        for (Vertex v: adjacents) { // add all the adjacents
            if (!visited.containsKey(v)) { // but only if it hasn't already been traversed
                visited.put(v, current);
                queue.add(v);
            }
        }

        current = queue.remove(); // goes to the next vertex
    }

    // create the path
    LinkedList<Vertex> path = new LinkedList<>();
    path.addFirst(current);
    while (current != startVert) {
        current = visited.get(current);
        path.addFirst(current); // addFirst is used to get the correct order
    }

    return path;
}

接下来的几张图片展示了这段代码的具体运作。

解决迷宫的问题

如果你想知道,用这个方法解决迷宫,你所需要做的就是把它变成一个图形

A maze. The maze contains a lot of black circles with letters inside of them, connected by red lines