圖(1)--圖的表示和搜尋
阿新 • • 發佈:2022-01-06
一、圖的概念
- 圖:圖是由一組頂點和一組能夠將兩個頂點相連的邊組成的
- 頂點:用一張符號表來為頂點的名字和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 (inti = 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+"--"); } } }