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

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

1、無向圖數據結構算法

接口:數組

/**
 * 圖論接口
 */
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();
        }
    }
}

2、深度搜索優先算法less

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

假設有如下圖的結構函數

 左側數組表示節點,右側表明與節點鏈接的其餘節點。該結構的標準畫法以下:this

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

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

// 接口
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點在路徑上則表示連通。

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

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

路徑搜索接口和實現:

/**
 * 尋找路徑
 */
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

相關文章
相關標籤/搜索