@author QYX
寫作時間:2013/0302 最近准備noi比賽,加油!!!
因為近期學習任務太多太緊,所以我主要維護Github,博客園可能會停更幾天。----2020年2月9日
圖
圖(graph)是用線連接在一起的頂點或節點的集合,即兩個要素:邊和頂點。每一條邊連接個兩個頂點,用(i,j)表示頂點為 i 和 j 的邊。
如果用圖示來表示一個圖,一般用圓圈表示頂點,線段表示邊。有方向的邊稱為有向邊,對應的圖成為有向圖,沒有方向的邊稱為無向邊,對應的圖叫無向圖。對於無向圖,邊(i, j)和(j,i)是一樣的,稱頂點 i 和 j 是鄰接的,邊(i,j)關聯於頂點 i 和 j ;對於有向圖,邊(i,j)表示由頂點 i 指向頂點 j 的邊,即稱頂點 i 鄰接至頂點 j ,頂點 i 鄰接於頂點 j ,邊(i,j)關聯至頂點 j 而關聯於頂點 i 。
對於很多的實際問題,不同頂點之間的邊的權值(長度、重量、成本、價值等實際意義)是不一樣的,所以這樣的圖被稱為加權圖,反之邊沒有權值的圖稱為無權圖。所以,圖分為四種:加權有向圖,加權無向圖,無權有向圖,無權無向圖。
圖的表現有很多種,鄰接表法,臨接矩陣等。
圖經常是以這種形式出現的[weight,from,to]的n*3維數組出現的,見名知意,三個元素分別為邊的權重,從哪兒來,到哪兒去。
如上圖所示,由一條邊連接在一起的頂點稱為相鄰頂點,A和B是相鄰頂點,A和D是相鄰頂點,A和C是相鄰頂點......A和E是不相鄰頂點。一個頂點的度是其相鄰頂點的數量,A和其它三個頂點相連,所以A的度為3,E和其它兩個頂點相連,所以E的度為2......路徑是一組相鄰頂點的連續序列,如上圖中包含路徑ABEI、路徑ACDG、路徑ABE、路徑ACDH等。簡單路徑要求路徑中不包含有重復的頂點,如果將環的最后一個頂點去掉,它也是一個簡單路徑。例如路徑ADCA是一個環,它不是一個簡單路徑,如果將路徑中的最后一個頂點A去掉,那么它就是一個簡單路徑。如果圖中不存在環,則稱該圖是無環的。如果圖中任何兩個頂點間都存在路徑,則該圖是連通的,如上圖就是一個連通圖。如果圖的邊沒有方向,則該圖是無向圖,上圖所示為無向圖,反之則稱為有向圖,下圖所示為有向圖:
在有向圖中,如果兩個頂點間在雙向上都存在路徑,則稱這兩個頂點是強連通的,如上圖中C和D是強連通的,而A和B是非強連通的。如果有向圖中的任何兩個頂點間在雙向上都存在路徑,則該有向圖是強連通的,非強連通的圖也稱為稀疏圖。
此外,圖還可以是加權的。前面我們看到的圖都是未加權的,下圖為一個加權的圖:
可以想象一下,前面我們介紹的樹和鏈表也屬於圖的一種特殊形式。圖在計算機科學中的應用十分廣泛,例如我們可以搜索圖中的一個特定頂點或一條特定的邊,或者尋找兩個頂點間的路徑以及最短路徑,檢測圖中是否存在環等等。
存在多種不同的方式來實現圖的數據結構,下面介紹幾種常用的方式。
鄰接矩陣
在鄰接矩陣中,我們用一個二維數組來表示圖中頂點之間的連接,如果兩個頂點之間存在連接,則這兩個頂點對應的二維數組下標的元素的值為1,否則為0。下圖是用鄰接矩陣方式表示的圖:
如果是加權的圖,我們可以將鄰接矩陣中二維數組里的值1改成對應的加權數。鄰接矩陣方式存在一個缺點,如果圖是非強連通的,則二維數組中會有很多的0,這表示我們使用了很多的存儲空間來表示根本不存在的邊。另一個缺點就是當圖的頂點發生改變時,對於二維數組的修改會變得不太靈活。
鄰接表
圖的另外一種實現方式是鄰接表,它是對鄰接矩陣的一種改進。鄰接表由圖中每個頂點的相鄰頂點列表所組成。如下圖所示,我們可以用數組、鏈表、字典或散列表來表示鄰接表。
關聯矩陣
我們還可以用關聯矩陣來表示圖。在關聯矩陣中,矩陣的行表示頂點,列表示邊。關聯矩陣通常用於邊的數量比頂點多的情況下,以節省存儲空間。如下圖所示為關聯矩陣方式表示的圖:
深度優先搜索#
深度優先搜索,我們以無向圖為例。
圖的深度優先搜索(Depth First Search),和樹的先序遍歷比較類似。
它的思想:假設初始狀態是圖中所有頂點均未被訪問,則從某個頂點v出發,首先訪問該頂點,然后依次從它的各個未被訪問的鄰接點出發深度優先搜索遍歷圖,直至圖中所有和v有路徑相通的頂點都被訪問到。 若此時尚有其他頂點未被訪問到,則另選一個未被訪問的頂點作起始點,重復上述過程,直至圖中所有頂點都被訪問到為止。
顯然,深度優先搜索是一個遞歸的過程。
廣度優先搜索#
廣度優先搜索,我們以有向圖為例。
廣度優先搜索算法(Breadth First Search),又稱為”寬度優先搜索”或”橫向優先搜索”,簡稱BFS。
它的思想是:從圖中某頂點v出發,在訪問了v之后依次訪問v的各個未曾訪問過的鄰接點,然后分別從這些鄰接點出發依次訪問它們的鄰接點,並使得“先被訪問的頂點的鄰接點先於后被訪問的頂點的鄰接點被訪問,直至圖中所有已被訪問的頂點的鄰接點都被訪問到。如果此時圖中尚有頂點未被訪問,則需要另選一個未曾被訪問過的頂點作為新的起始點,重復上述過程,直至圖中所有頂點都被訪問到為止。
換句話說,廣度優先搜索遍歷圖的過程是以v為起點,由近至遠,依次訪問和v有路徑相通且路徑長度為1,2…的頂點。
package com.qyx; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; //使用鄰接矩陣來實現圖 public class Graph { private ArrayList<String> vertexList; //存儲頂點的集合 private int[][] edges;//存儲圖對應的鄰接矩陣 private int numOfEdges;//表示邊的數目 private boolean isVisited[]; public static void main(String[] args) { int n=5;//節點的個數 String vertex[]={"A","B","C","D","E"}; //創建圖對象 Graph graph=new Graph(n); for (String s:vertex) { graph.vertexList.add(s); } //添加邊 graph.insertEdge(0,1,1); graph.insertEdge(0,2,1); graph.insertEdge(1,2,1); graph.insertEdge(1,3,1); graph.insertEdge(1,4,1); //顯示鄰接矩陣 graph.showGraph(); //graph.dfs(); graph.bfs(); } //構造器 public Graph(int n) { //初始化鄰接矩陣和vertexList edges=new int[n][n]; vertexList=new ArrayList<String>(); numOfEdges=0; isVisited=new boolean[5]; } //插入頂點 public void insertVertex(String vertex) { vertexList.add(vertex); } /** * * @param v1 表示點的下標 即是第幾個頂點 * @param v2 第二個頂點對應的下標 * @param weight 表示是否連接 0不相連 1相連 */ //添加邊 public void insertEdge(int v1,int v2,int weight) { edges[v1][v2]=weight; edges[v2][v1]=weight; numOfEdges++; } //圖中常用的方法 //返回節點的個數 public int getNumOfVertex() { return vertexList.size(); } //得到邊的數目 public int getNumOfEdges() { return numOfEdges; } //返回節點i對應的值 0-A 1-B 2-C public String getValue(int index) { return vertexList.get(index); } //返回v1和v2的權值 public int getWeight(int v1,int v2) { return edges[v1][v2]; } //顯示圖對應的鄰接矩陣 public void showGraph() { for (int[] arrs:edges) { System.out.println(Arrays.toString(arrs)); } } /** * * @param index * @return 如果存在返回對應的下標,否則返回-1 */ //得到第一個鄰接節點的下標 public int getFirstNeighbor(int index) { for (int j=index;j<vertexList.size();j++) { if (edges[index][j]>0) { return j; } } return -1; } //根據前一個鄰接節點的下標來獲取下一個鄰接節點 public int getNextNeighbor(int v1,int v2) { for (int i = v2 + 1; i < vertexList.size(); i++) { if (edges[v1][i] > 0) { return i; } } return -1; } //深度優先遍歷算法 //i 第一次就是0 private void dfs ( boolean[] isVisited, int i) { //首先我們訪問該節點 System.out.print(getValue(i) + "->"); //將該鄰接節點設置為已訪問 isVisited[i] = true; int w = getFirstNeighbor(i);//查找節點w的第一個鄰接節點w while (w != -1) { if (!isVisited[w]) { dfs(isVisited,w); } //如果已經被訪問過 w=getNextNeighbor(i,w); } } //對dfs進行重載,遍歷所有的節點並進行dfs public void dfs() { //遍歷所有的節點進行dfs for (int i=0;i<getNumOfVertex();i++) { if (!isVisited[i]) { dfs(isVisited,i); } } } //對一個節點進行深度優先遍歷的方法 public void bfs(boolean[] isVisited,int i) { int u;//表示隊列的頭結點對應下標 int w;//鄰接節點 //隊列,記錄節點訪問的順序 LinkedList queue = new LinkedList(); //訪問節點 System.out.print(getValue(i)+"->"); //標記為已訪問 isVisited[i]=true; //將節點加入隊列 queue.addLast(i); while (!queue.isEmpty()) { //取出隊列的頭結點下標 u =(Integer)queue.removeFirst(); //得到第一個鄰接節點的下標w w =getFirstNeighbor(u); while (w!=-1) { //找到 //是否訪問 if (!isVisited[w]) { System.out.print(getValue(w)+"->"); //入隊 queue.addLast(w); isVisited[w]=true; } //找w后面的下一個鄰接節點 w=getNextNeighbor(u,w); //體現出廣度優先 } } } //遍歷所有的節點都進行廣度優先搜索 public void bfs() { for (int i=0;i<getNumOfVertex();i++) { if (!isVisited[i]) { bfs(isVisited,i); } } } }
2020年2月9日對原有博客進行了修改,參考博客:
https://www.cnblogs.com/jaxu/p/11338294.html
https://www.cnblogs.com/DarrenChan/p/9547869.html
感謝幫助!