無向圖:學習整理

無向圖

使用的數據結構:

  • 鄰接表:使用一個以頂點爲索引的列表數組 – 每個元素都是和該頂點相鄰的頂點列表
    • 空間和V+E成正比
    • 添加一條條邊所需的時間爲常數
    • 遍歷頂點v的所有相鄰頂點所需的時間和v的度數成正比

基本數據結構:

public class Graph {

    private final int V;
    private int E;
    private Bag<Integer>[] adj; //鄰接表

    public Graph(int V) {
        this.V = V;
        this.E = 0;
        // 充當一條鏈表,存儲相鄰的節點
        Bag<Integer>[] bags = (Bag<Integer>[]) new Bag[V];
        for (int i = 0; i < V; i++) {
            adj[i] = new Bag<>();
        }
    }

    public int E() {
        return E;
    }

    public int V() {
        return V;
    }

    public void addEdge(int v, int w) {
        adj[v].add(w);
        adj[w].add(v);
        E++;
    }
	// 遍歷相鄰的節點
    public Iterable<Integer> adj(int v) {
        return adj[v];
    }
}

深度優先搜索(DFS)

核心方法:訪問一個頂點時

  • 將它標記爲已訪問
  • 遞歸地訪問它的所有沒有被標記過的鄰近頂點
  • DFS中每條邊總會被訪問量詞,且在第二次時會發現這個頂點已經被標記過
public class DepthFirstPaths {
    private boolean[] marked;
    // 比如需要從3開始遍歷到4,5;5找到後返回到,再從3到4
    // 那麼edgeTo[5] = 3; edgeTo[4] = 3; -- 都是從3到這兩個點
    private int[] edgeTo; // 從起點到一個頂點的已知路徑上的最後一個頂點
    private final int s;

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

    private void dfs(Graph G, int v) {
        // 訪問到該頂點,置爲true
        marked[v] = true;
        for (int w : G.adj(v)) {
            // 如果沒有訪問過
            if (!marked[w]) {
                // 將路徑添加到數組中
                edgeTo[w] = v;
                dfs(G, v);
            }
        }
    }

    public boolean hasPathTo(int v) {
        return marked[v];
    }

    public Iterable<Integer> pathTo(int v) {
        if(!hasPathTo(v)) return null;
        Stack<Integer> path = new Stack<>();
        // 根據路徑點,依次入棧,最後將起點入棧,後進先出彈出。得到路徑
        for (int i = v; i != s ; i = edgeTo[i]) {
            path.push(i);
        }
        path.push(s);
        return path;
    }

}

DFS應用:檢測連通分量:

// 記錄連通分量
public class CC {
    private boolean[] marked;
    // 記錄頂點所屬的連通分量的id
    private int[] id;
    private int count;

    public CC(Graph G) {
        marked = new boolean[G.V()];
        id = new int[G.V()];
        for (int i = 0; i < G.V(); i++) {
            if (!marked[i]) {
                // 執行完一次DFS,遍歷這個點可達的所有頂點。
                // 這些頂點構成了一個連通分量
                dfs(G, i);
                count++;
            }
        }
    }

    private void dfs(Graph G, int s) {
        marked[s] = true;
        id[s] = count;
        for (int w : G.adj(s)) {
            if (!marked[s]) {
                dfs(G, w);
            }
        }
    }

    public boolean connected(int v, int w){
        // 如果所屬的連通分量id相同,則證明v和w相互連通
        return id[v] == id[w];
    }
}

廣度優先搜索 – BFS

比喻:廣度優先搜索就像是一組人在一起朝各個方向走這座迷宮。

通過棧(LIFO)來描述DFS,用隊列(FIFO)來描述BFS

算法:

  • 取隊列中的一下一個頂點並標記它
  • 將於v相鄰的所有未被標記過的頂點加入隊列,並標記
  • 注意入隊的順序是有嚴格要求的,因爲是將結點的鄰接結點進行的入隊
private void bfs(Graph G, int s) {
    // 用LinkedList來實現隊列操作
    Queue<Integer> queue = new LinkedList<>();
    marked[s] = true;
    // 加入隊首
    queue.offer(s);
    while (!queue.isEmpty()) {
        // 取出隊尾元素
        int v = queue.poll();
        for (int w : G.adj(v)) {
            // 將所有未被標記的元素加入隊列
            if (!marked[w]) {
                marked[w] = true;
                queue.offer(w);
                edgeTo[w] = v;
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章