1. 程式人生 > >小橙書閱讀指南(十二)——無向圖、深度優先搜索和路徑查找算法

小橙書閱讀指南(十二)——無向圖、深度優先搜索和路徑查找算法

val 短路徑 clas 圖論 bsp 檢查 inf targe 自然

在計算機應用中,我們把一系列相連接的節點組成的數據結構,叫做圖。今天我們將要介紹它的一種形式——無向圖,以及針對這種結構的深度優先搜索和路徑查找算法。

一、無向圖數據結構

接口:

/**
 * 圖論接口
 */
public interface Graph {
    /**
     * 頂點數
     *
     * @return
     */
    int vertexNum();

    /**
     * 邊數
     *
     * @return
     */
    int edgeNum();

    /**
     * 向圖中添加一條v-w的邊
     *
     * 
@param v * @param w */ void addEdge(int v, int w); /** * 和v相鄰的所有頂點 * * @param v * @return */ Iterable<Integer> adjoin(int v); /** * v的維度 * * @param v * @return */ int degree(int v); }

實現類:

public class Graph implements
algorithms.graphs.ifs.Graph { private final int vertex; // 頂點 private int edge; // private ArrayList<Integer>[] adj; public Graph(int v) { this.vertex = v; this.edge = 0; adj = (ArrayList<Integer>[]) new ArrayList[v]; for (int i = 0; i < v; i++) { adj[i]
= new ArrayList<>(); } } @Override public int vertexNum() { return vertex; } @Override public int edgeNum() { return edge; } @Override public void addEdge(int v, int w) { validateVertex(v); validateVertex(w); adj[v].add(w); adj[w].add(v); edge++; } @Override public Iterable<Integer> adjoin(int v) { return adj[v]; } @Override public int degree(int v) { return adj[v].size(); } private void validateVertex(int v) { if (v < 0 || v > this.vertex) { throw new IllegalArgumentException(); } } }

二、深度搜索優先算法

對於圖的處理我們常常通過系統地檢查每一個頂點和每一條邊來獲取圖的各種性質。對於圖的問題我們最經常被問及的是:a點和b點連通嗎?如果連通如何到達?為了描述方便,我們使用自然數描述圖的每一個頂點。

假設有以下圖的結構

技術分享圖片

左側數組表示節點,右側代表與節點連接的其他節點。該結構的標準畫法如下:

技術分享圖片

算法描述:深度優先搜索從起點出發(0)遍歷(2,1,5)並遞歸(2)與它鏈接的點,被搜索到的點將不會被再次遞歸直到所有的點都被搜索到為止。

深度優先搜索接口與實現:

// 接口
public interface Search {
    boolean marked(int v);
    int count();
}

/**
 * 圖論:深度優先搜索
 */
public class DepthFirstSearch implements Search {
    private boolean[] marked;
    private int count;

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

    /**
     * 以遞歸的方式從s起點出發,標記每一個經過的頂點,未被標記的頂點為不連通
     *
     * @param g
     * @param v
     */
    private void dfs(Graph g, int v) {
        marked[v] = true;
        count++;
        for (int x : g.adjoin(v)) {
            if (!marked[x]) {
                dfs(g, x);
            }
        }
    }

    @Override
    public boolean marked(int v) {
        validateVertex(v);
        return marked[v];
    }

    @Override
    public int count() {
        return count;
    }

    // throw an IllegalArgumentException unless {@code 0 <= vertexNum < V}
    private void validateVertex(int v) {
        int V = marked.length;
        if (v < 0 || v >= V)
            throw new IllegalArgumentException();
    }

}

這套算法的核心是dfs函數。我們要理解深度優先算法就必須弄清楚算法遞歸的過程。marked數組記錄節點的訪問情況,變量x和v的遞歸過程如下:

技術分享圖片

標準畫法:

技術分享圖片

深度優先算法按照上面的路徑搜索圖,由此我們可以獲知深度搜索算法的兩個特征:

1.搜索路徑沿一條路徑向下擴展,每一個節點只會被遍歷一次(每一個節點都可以知道在搜索路徑上的上一個節點,並唯一確定)。

2.搜索路徑上的任意兩點代表可達,但並非最短路徑。

這樣我們就可以回答本文最早提出的有關圖的第一個問題:a點和b點連通嗎?顯然,以a為起點搜索整個圖,如果b點在路徑上則表示連通。

三、使用深度優先搜索的路徑算法

要回答有關圖的第二個問題:如果連通如何到達?回憶上一段我們總結的深度搜索算法的第一個特征,我們可以使用數組結構在存儲每一個節點的上一個節點。

路徑搜索接口和實現:

/**
 * 尋找路徑
 */
public interface Paths {
    boolean hasPathTo(int vertex);

    Iterable<Integer> pathTo(int vertex);
}

/**
 * 基於深度優先搜索的路徑搜索算法
 */
public class DepthFirstPaths implements Paths {
    private final int s;
    private boolean[] marked;
    private int[] edgeTo;

    public DepthFirstPaths(Graph g, int s) {
        this.s = s;
        marked = new boolean[g.vertexNum()];
        edgeTo = new int[g.vertexNum()];
        dfs(g, s);
    }

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

    @Override
    public boolean hasPathTo(int vertex) {
        return marked[vertex];
    }

    @Override
    public Iterable<Integer> pathTo(int vertex) {
        if (!hasPathTo(vertex)) {
            return null;
        }
        Stack<Integer> path = new Stack<>();
        for (int i = vertex; i != s; i = edgeTo[i]) {
            path.push(i);
        }
        path.push(s);
        return path;
    }
}

算法的標準畫法:

技術分享圖片

相關鏈接:

Algorithms for Java

小橙書閱讀指南(十二)——無向圖、深度優先搜索和路徑查找算法