1. 程式人生 > >無權重最短路徑問題:廣度優先搜尋 & Java 實現

無權重最短路徑問題:廣度優先搜尋 & Java 實現

一、什麼是圖

通俗的說,圖就是由 頂點(Vertex)邊(edge) 組成的。一般來說又分為 有向圖無向圖,即頂點到頂點的邊是否是有方向區分的。

二、廣度優先搜尋

1. 概念

廣度優先搜尋(Breadth First Search)通常用於指出是否存在一條路徑由頂點 A 到達頂點 B,如果有,則找出頂點 A 到頂點 B 的最短路徑。 注意,這裡的最短路徑是沒有權重概念的,即可以認為任一邊的長度均為 1。

2. 步驟

首先,我們從頂點 A 出發,找出它的所有直接鄰居點(可以想象為一度人脈),檢查這裡面是否存在頂點 B。 然後,假設上述 “一度人脈” 中不存在頂點 B,這將鄰居點的鄰居點(可以想象為二度人脈)也加入到搜尋隊列中,並檢查其中是否存在頂點 B。 如上,我們依次擴大到 “三度人脈”、“四度人脈” … 直到找到頂點 B 為止。 如果找完了都找不到頂點 B 的話,則說明圖中不存在頂點 A 到頂點 B 的路徑。

注意: 找過的點務必不要再去檢查,否則可能導致無限迴圈!

3. 執行時間

廣度優先搜尋的執行時間為 O(頂點數 + 邊數),通常記為 O(V + E)。

三、例項分析

1. 如下給定一個圖,求 “頂點0” 到 “頂點8” 的最短路徑

在這裡插入圖片描述

2. 思路分析

  • 首先我們可以想到,頂點是一類擁有共同特性的物件,因此我們可以建立一個 Vertex 類來表示圖中的頂點。它應該包含 自己的id有哪些鄰居 等基本特性。
  • 其次為了程式碼實現和記錄搜尋路徑,Vertex 類還需要一個 是否被檢查過它的上一個頂點是誰 這兩個屬性。
  • 然後,我們選擇 佇列 作為我們的資料結構,我們先將起始點的直接鄰居全部新增到佇列中去,依次從隊首取出元素比較,如果不是我們要找的點這將其鄰居(這裡就是類似第二度人脈了)新增到隊尾。
  • 對於檢查過的點,我們選擇跳過,避免無限迴圈。
  • 我們只需要重複上述過程,直到找到終點或是佇列為空時結束。

3. 程式碼實現

首先建立一個 Vertex 類,如下:

import java.util.LinkedList;

public class Vertex {

    private int id; // 頂點的標識
    private LinkedList<Vertex> neighbors; // 這個頂點的鄰居頂點
    private boolean isSerched; // 這個頂點是否被搜尋過了
    private Vertex predecessor; // 這個頂點的前驅,用來記錄路徑的
public Vertex(int id) { this.id = id; } public void addNeighbor(Vertex... vertexes) { this.neighbors = new LinkedList<>(); for (Vertex vertex : vertexes) { this.neighbors.add(vertex); } } public int getId() { return id; } public void setId(int id) { this.id = id; } public LinkedList<Vertex> getNeighbors() { return neighbors; } public void setNeighbors(LinkedList<Vertex> neighbors) { this.neighbors = neighbors; } public boolean isSerched() { return isSerched; } public void setSerched(boolean isSerched) { this.isSerched = isSerched; } public Vertex getPredecessor() { return predecessor; } public void setPredecessor(Vertex predecessor) { this.predecessor = predecessor; } }

然後,建立一個場景類,並實現和呼叫 BFS 方法:

import java.util.LinkedList;

public class Main {

    public static void main(String[] args) {
        // init data (對照上面題目給的圖)
        Vertex[] vertexes = new Vertex[9];
        for (int i = 0; i < vertexes.length; i++) {
            vertexes[i] = new Vertex(i);
        }
        vertexes[0].addNeighbor(vertexes[1], vertexes[3]);
        vertexes[1].addNeighbor(vertexes[0], vertexes[2]);
        vertexes[2].addNeighbor(vertexes[1], vertexes[4], vertexes[5]);
        vertexes[3].addNeighbor(vertexes[0], vertexes[5], vertexes[6]);
        vertexes[4].addNeighbor(vertexes[2], vertexes[8]);
        vertexes[5].addNeighbor(vertexes[2], vertexes[3], vertexes[6], vertexes[8]);
        vertexes[6].addNeighbor(vertexes[3], vertexes[5], vertexes[7]);
        vertexes[7].addNeighbor(vertexes[6], vertexes[8]);
        vertexes[8].addNeighbor(vertexes[4], vertexes[5], vertexes[7]);
        // start search
        Vertex start = vertexes[0];
        Vertex end = vertexes[8];
        boolean hasPath = bfs(start, end);
        if (hasPath) {
            // print path
            Vertex predecessor = end;
            do {
                System.out.print(predecessor.getId() + " -> ");
                predecessor = predecessor.getPredecessor();
            } while (predecessor != null);
        } else {
            System.out.println("不存在該起點到該終點的路徑");
        }
    }

    public static boolean bfs(Vertex start, Vertex end) {
        LinkedList<Vertex> queue = new LinkedList<>();
        queue.addLast(start); // 新增到隊尾
        while (!queue.isEmpty()) {
            Vertex vertex = queue.pollFirst(); // 從隊首取出一個元素
            // 如果這個頂點是否已經檢索過了,則跳過
            if (vertex.isSerched()) {
                continue;
            }

            if (vertex == end) {
                // 如果到了終點了就可以返回了
                return true;
            } else {
                // 如果還不是終點,這把該頂點可以到達的鄰居全部新增到佇列末端
                for (Vertex neighbor : vertex.getNeighbors()) {
                    if (neighbor.getPredecessor() == null && neighbor != start) {
                        neighbor.setPredecessor(vertex);
                    }
                    queue.addLast(neighbor);
                }
            }
            vertex.setSerched(true);
        }
        return false;
    }

}

執行結果:

8 -> 5 -> 3 -> 0 ->