1. 程式人生 > 其它 >圖(1)--圖的表示和搜尋

圖(1)--圖的表示和搜尋

一、圖的概念

  • 圖:圖是由一組頂點和一組能夠將兩個頂點相連的邊組成的
  • 頂點:用一張符號表來為頂點的名字和0到V-1的整數值建立一一對應的關係,頂點可以表示一個城市,一個網頁等
  • 邊:兩個頂點之間的連線關係
  • 鄰接:兩個頂點通過一條邊相連,說明兩個節點鄰接,這條邊依附於這兩個頂點
  • 子圖:由一幅圖的所有邊的一個子集(包括它們所依附的所有頂點)組成的圖
  • 路徑:由邊順序連線的一系列頂點,路徑的長度為其所包含的邊數
  • 連通圖:從任意一個頂點都存在一條路徑到達另一個任意頂點的圖
  • 非連通圖:一般由若干個連通的部分組成,他們都是其極大連通子圖
  • 無環圖:不包含環的圖
  • 生成樹森林:所有連通子圖的生成樹的集合
  • 自環:一條連線一個頂點和其自身的邊
  • 平行邊:連線同一對頂點的兩條邊
  • 環:一條至少含有一條邊且起點和終點相同的路徑
  • 無向圖:邊僅僅連線兩個頂點,沒有其他含義
  • 有向圖:邊不僅連線兩個頂點,並且具有方向
  • 度:某個頂點的度數依附於他的邊的總數

二、圖的表示

  • 要表示一幅圖,只需要表示清楚以下兩部分內容即可:圖中所有的頂點+所有連線頂點的邊
  • 鄰接矩陣:使用一個V*V的二維陣列int[V][V] adj,把索引的值看做是頂點;如果頂點v和頂點w相連,我們只需要將adj[v][w]和adj[w][v]的值設定為1,否則設定為 0 。鄰接矩陣這種儲存方式的空間複雜度是V^2的,如果我們處理的問題規模比較大的話,記憶體空間極有可能不夠用。
  • 鄰接表:使用一個大小為V的陣列 Queue[V] adj,把索引看做是頂點;每個索引處adj[v]儲存了一個佇列,該佇列中儲存的是所有與該頂點相鄰的其他頂點
  • 建立圖的表示類
package 圖;


import java.util.LinkedList;
import java.util.Queue;

public class Graph1 {
    private int V; //頂點數
    private int E;//邊數
    private Queue<Integer>[] adj;//鄰接表

    public Graph1(int V) {
        this.V = V;
        this.E = 0;
        this.adj = new Queue[V];
        for (int
i = 0; i < V; i++) { adj[i] = new LinkedList<Integer>(); } } //獲取頂點數 public int getV() { return V; } //獲取邊數 public int getE() { return E; } //向圖中新增一條邊 v-w public void addEdge(int v, int w) { /* 在無向圖中,邊是沒有方向的,所以該邊既可以說是從v到w的邊, 又可以說是從w到v的邊,因此,需要讓w出現在v的鄰接表中,並且還要讓v出現在w的鄰接表中 */ adj[v].offer(w); adj[w].offer(v); E++; }
//向圖中新增一條邊 v-w,方法過載
public void addEdge(int... Edges){ if(Edges.length%2==0){ for (int i = 0; i <Edges.length ; i+=2) { addEdge(Edges[i],Edges[i+1]); } }else throw new IllegalStateException("頂點數必須為2的整數倍"); } // 獲取和V相鄰的所有節點 public Queue<Integer> adj(int V) { return adj[V]; } // 列印節點 @Override public String toString() { StringBuilder s = new StringBuilder("(" + V + " vertices, " + E + " edges)\n"); for (int v = 0; v < V; v++) { s.append(v).append(": "); for (int w : this.adj(v)) { s.append(w).append(" "); } s.append("\n"); } return s.toString(); } public static void main(String[] args) { Graph1 g = new Graph1(13); g.addEdge(0,1,0,2,0,6,0,5,3,5,3,4,4,5,6,4,7,8,9,10,9,11,9,12,11,12); System.out.println(g); //構建圖 } }

三、深度優先搜尋

深度優先搜尋:要搜尋一幅圖,只需要一個遞迴方法來遍歷所有頂點。在訪問其中一個節點時將它標記為已訪問,並遞迴的訪問它的所有沒有被標記過的鄰居頂點。

在搜尋時,如果遇到一個結點既有子結點,又有兄弟結點,那麼先找子結點,然後找兄弟結點。常用於連通分量的判斷

package 圖;

import java.util.Stack;

public class DepthFirstPaths {
    private Graph1 graph;
    private int s;//起點
    private int[] edgeTo;//從起點到一個頂點的已知路徑上的最後一個頂點
    private boolean[] visited;//標記已訪問過的頂點

    public DepthFirstPaths(Graph1 graph, int s) {
        this.graph = graph;
        this.s = s;
        this.edgeTo=new int[graph.getV()];
        visited=new boolean[graph.getV()];
        dfs(graph,s);
    }
    //    深度優先搜尋
    public void dfs(Graph1 graph, int s) {
       visited[s]=true;
        for (Integer v : graph.adj(s)) {
            if(!visited[v]){
                edgeTo[v]=s;
                dfs(graph,v);
            }
        }
    }

//    是否存在路徑
    public boolean hasPathTo(int v){
       return visited[v];
    }
    //    存在起點到終點的路徑
    public Iterable<Integer> pathTo(int v){
        if(!hasPathTo(v))return null;
        Stack<Integer> path=new Stack<>();
        for (int x = v; x!=s ; x=edgeTo[x]) {
            path.push(x);
        }
        path.push(s);
        return path;
    }
    public static void main(String[] args) {

        //準備Graph物件
        Graph1 G = new Graph1(13);
        G.addEdge(0,1);
        G.addEdge(0,2);
        G.addEdge(0,6);
        G.addEdge(0,5);
        G.addEdge(5,3);
        G.addEdge(5,4);
        G.addEdge(3,4);
        G.addEdge(4,6);
        G.addEdge(7,8);

        G.addEdge(9,11);
        G.addEdge(9,10);
        G.addEdge(9,12);
        G.addEdge(11,12);

        System.out.println(G);
        System.out.println("----------");
        DepthFirstPaths paths = new DepthFirstPaths(G, 0);
        Iterable<Integer> iterable = paths.pathTo(3);
        for (Integer integer : iterable) {
            System.out.print(integer+"--");
        }
    }
}

四、廣度優先搜尋

廣度優先搜尋:求單點最短路徑常用的經典方法,可以用於求間隔的度數。

在搜尋時,如果遇到一個結點既有子結點,又有兄弟結點,那麼先找兄弟結點,然後找子結點。

package 圖;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class BreadthFirstPaths {
    private Graph1 graph;
    private int s;//起點
    private int[] edgeTo;//從起點到一個頂點的已知路徑上的最後一個頂點
    private boolean[] visited;//標記已訪問過的頂點

    public BreadthFirstPaths(Graph1 graph, int s) {
        this.graph = graph;
        this.s = s;
        this.edgeTo=new int[graph.getV()];
        visited=new boolean[graph.getV()];
        bfs(graph,s);
    }
    //    廣度優先搜尋,找到的一定是最短的路徑,但並不一定是唯一的最短路徑
    public void bfs(Graph1 graph, int s) {
        Queue<Integer> queue = new LinkedList<>();
        visited[s]=true; //標記起點
        queue.offer(s); //加入佇列
        while (!queue.isEmpty()){
            Integer w = queue.poll();  //取出鄰接的一個頂點
            for (Integer v : graph.adj(w)) {  //獲取該頂點的鄰接節點
                if(!visited[v]){//如果沒有被訪問,就標記邊,併入隊
                    edgeTo[v]=w;
                    visited[v]=true;
                    queue.offer(v);
                }
            }
            }
        }
    //    是否存在路徑
    public boolean hasPathTo(int v){
        return visited[v];
    }
    //    存在起點到終點的路徑
    public Iterable<Integer> pathTo(int v){
        if(!hasPathTo(v))return null;
        Stack<Integer> path=new Stack<>();
        for (int x = v; x!=s ; x=edgeTo[x]) {
            path.push(x);
        }
        path.push(s);
        return path;
    }  public static void main(String[] args) {

        //準備Graph物件
        Graph1 G = new Graph1(13);
        G.addEdge(0,1);
        G.addEdge(0,2);
        G.addEdge(0,6);
        G.addEdge(0,5);
        G.addEdge(5,3);
        G.addEdge(5,4);
        G.addEdge(3,4);
        G.addEdge(4,6);
        G.addEdge(7,8);

        G.addEdge(9,11);
        G.addEdge(9,10);
        G.addEdge(9,12);
        G.addEdge(11,12);

        System.out.println(G);
        System.out.println("----------");
        BreadthFirstPaths paths = new BreadthFirstPaths(G, 0);
        Iterable<Integer> iterable = paths.pathTo(4);
        for (Integer integer : iterable) {
            System.out.print(integer+"--");
        }
    }
}