圖的基本概念表示方法以及兩種搜尋方式——深度優先遍歷和廣度優先遍歷
阿新 • • 發佈:2019-01-22
原先的知識沒好好學,導致現在很多都忘了,或者說以前就沒弄懂過。現在重新看一遍,收穫良多,不管怎樣先寫這篇基礎的,當做筆記。
圖的定義:是由頂點的有窮非空集合和頂點之間的邊的集合組成的,通常表示為 G(V,E)。其中G表示一個圖,V是圖的頂點的集合,E是圖的邊的集合。
有跟多條邊的圖我們稱為稠密圖,很少條邊的我們稱為稀疏圖。
有向圖和無向圖:
無向圖:頂點之間的邊是沒有方向的,也就是兩個方向互通的。比如頂點 Vi 和頂點 Vj 之間的邊,用(Vi,Vj)表示。
有向圖:頂點間的邊是有方向的,稱為有向邊,也成為 弧(Arc)。比如頂點Vi指向頂點Vj 的弧,用有序的偶:
<Vi,Vj> 表示,Vi稱為弧尾,Vj 稱為弧頭。
與圖的邊或者弧有關的數叫做權(權值,Weight),帶權的圖通常稱為網。
連通圖:如果頂點Vi 到Vj 有路徑,則稱Vi 和Vj 是連通的。如果對於圖中的任意兩個頂點 Vi 和 Vj 是連通的則稱該圖為連通圖。
無向圖中的極大連通子圖稱為連通分量。
度:一個頂點所連線的邊的數目,是對無向圖而言的
入度和出度:一個頂點指向其他頂點的弧的數目稱為出度,其他頂點指向該頂點的弧的數目稱為入度。
圖的儲存結構:鄰接矩陣、鄰接表、十字連結串列臨街多重表等。
鄰接矩陣的儲存方式:
用一個一維的陣列來儲存儲存圖中的頂點資訊,用一個二維的陣列來儲存圖中邊的或者弧的資訊。
鄰接矩陣簡單的定義圖的結構:
public class graph {
public char[] vexs; //頂點
public int[][] arc; //鄰接矩陣
int numVexs; //頂點數
int numEdges; //邊數
public graph(int nVexs,int nEdg){
this.numVexs = nVexs;
this.numEdges = nEdg;
}
}
我們先用一位陣列 vexs 儲存 n 個頂點的資訊,然後一個 n*n 的二維陣列 arc 儲存邊或者弧。arc[ i ][ j ] 等於 1 表示頂點 Vi 連通 Vj (如果是無向圖那麼 arc[ j ][ i ] 也為 1),等於 0 則說明這兩個頂點之間沒有邊或者弧 。package com.hunter.Graph;
/**
* 深度優先搜尋
* @author luzi
*
*/
public class graphDFS {
public void DFSraverse(graph gh){
//初始化visited陣列,用 false 以表示該頂點是否已經訪問過
boolean[] visited = new boolean[gh.numVexs];
for(int i = 0; i < gh.numVexs; i++){
visited[i] = false;
}
System.out.println();
for(int i = 0; i < gh.numVexs; i++){
if(!visited[i])
DFS(gh,i,visited);
}
}
private void DFS(graph gh, int k,boolean[] vi) {
// TODO Auto-generated method stub
vi[k] = true;
System.out.println("正訪問的結點是 : " + gh.vexs[k]);
for(int i = 0; i < gh.numVexs; i++){
if(gh.arc[k][i] == 1 && !vi[i])
DFS(gh,i,vi);
}
}
}
廣(寬)度優先遍歷(Breadth First Search):又稱為廣度優先搜尋,簡稱BFS 。 遍歷的過程是先從頂點 V0 出發,遍歷與 V0 直接連線而又未訪問過的頂點V1 、V2 、V3 等,再訪問 與 V1直接連線且未訪問過的頂點。同樣用一個數組來標記一個頂點是否已經訪問過,用一個佇列來儲存待訪問的頂點。 具體實現:
package com.hunter.Graph;
import java.util.LinkedList;
import java.util.Queue;
public class graphBFS {
public void BFSraverse(graph gh){
Queue<Integer> que = new LinkedList<Integer>();
boolean[] visited = new boolean[gh.numVexs];
for(int i = 0; i < gh.numVexs; i++)
visited[i] = false;
for(int i = 0; i < gh.numVexs; i++){
if(!visited[i]){
visited[i] = true;
System.out.println("正在訪問的節點是 :" + gh.vexs[i]);
que.add(i);
while(!que.isEmpty()){
que.remove();
for(int j = 0; j <gh.numVexs; j++){
if(gh.arc[i][j] == 1 && !visited[j]){
visited[j] = true;
System.out.println("正在訪問的節點是 :" + gh.vexs[j]);
que.add(j);
}
}
}
}
}
}
}
深度優先和寬度優先的時間複雜度是一樣的,但是訪問的順序不一樣。
深度優先類似二叉樹的先序遍歷而寬度優先類似二叉樹的層序遍歷。最後再看一個例子,有如下的無向圖:
初始化該圖,呼叫深度優先和寬度優先遍歷:
package com.hunter.Graph;
public class Demo {
public static void main(String args[]){
int numVexs = 7;
int numEdges = 6;
graph gh = new graph(numVexs,numEdges);
gh.vexs = new char[numVexs];
gh.vexs[0] = 'A';
gh.vexs[1] = 'B';
gh.vexs[2] = 'C';
gh.vexs[3] = 'D';
gh.vexs[4] = 'E';
gh.vexs[5] = 'F';
gh.vexs[6] = 'G';
gh.arc = new int[numVexs][numVexs];
gh.arc[0][1] = 1;
gh.arc[1][0] = 1;
gh.arc[0][4] = 1;
gh.arc[4][0] = 1;
gh.arc[1][2] = 1;
gh.arc[2][1] = 1;
gh.arc[2][3] = 1;
gh.arc[3][2] = 1;
gh.arc[3][4] = 1;
gh.arc[4][3] = 1;
gh.arc[2][5] = 1;
gh.arc[5][2] = 1;
gh.arc[1][5] = 1;
gh.arc[5][1] = 1;
gh.arc[6][3] = 1;
gh.arc[3][6] = 1;
System.out.println("********************廣度優先搜尋********************");
graphDFS ghDFS = new graphDFS();
ghDFS.DFSraverse(gh);
System.out.println("********************廣度優先搜尋********************");
graphBFS ghBFS = new graphBFS();
ghBFS.BFSraverse(gh);
}
}
實際執行結果: