打破你的認知!Java空指標居然還能這樣玩,90%人不知道…
一、圖的基本介紹●➢
●圖是一種資料結構,其中節點可以具有零個或多個相鄰元素。兩個節點之間的連線稱為邊。節點也可稱為頂點
圖的概念
二、圖的表示方式
●圖的表示方式有兩種:二維陣列表示(鄰接矩陣)、【陣列+連結串列】表示(鄰接表)
1.鄰接矩陣
●鄰接矩陣是表示圖形中頂點之間相鄰關係的矩陣,對於n個頂點的圖而言,矩陣是的row和col表示的是1...n個點。
2.鄰接表
●鄰接矩陣需要為每個頂點都分配n個邊的空間,其實有很多邊都是不存在,會造成空間的一定損失.
●鄰接表的實現只關心存在的邊,不關心不存在的邊。因此沒有空間浪費,鄰接表由【陣列+連結串列】組成
3.圖遍歷介紹
●所謂圖的遍歷,即是對結點的訪問。一個圖有那麼多個結點,如何遍歷這些結點,需要特定策略,一般有兩種訪問策略: (1)深度優先遍歷(2)廣度優先遍歷.
深度優先遍歷基本思想
●圖的深度優先搜尋(Depth First Search)。
➢深度優先遍歷,從初始訪問結點出發,初始訪問結點可能有多個鄰接結點,深度優先遍歷的策略就是首先訪問第一個鄰接結點,然後再以這個被訪問的鄰接結點作為初始結點,訪問它的第一個鄰接結點, 可以這樣理解: 每次都在訪問完當前結點後首先訪問當前結點的第一個鄰接結點。
➢我們可以看到,這樣的訪問策略是優先往縱向挖掘深入,而不是對一個結點的所有鄰接結點進行橫向訪問。
➢顯然,深度優先搜尋是一個遞迴的過程
深度優先遍歷演算法步驟
➢1)訪問初始結點v,並標記結點v為已訪問。
➢2)查詢結點v的第一一個鄰接結點w。
➢3)若w存在,則繼續執行4,如果w不存在,則回到第1步,將從v的下一個結點繼續。
➢4)若w未被訪問,對w進行深度優先遍歷遞迴(即把w當做另一個v, 然後進行步驟123)。
➢5)查詢結點v的w鄰接結點的下一個鄰接結點,轉到步驟3。
圖的廣度優先搜尋
圖的廣度優先搜尋(Broad First Search)。類似於一個分層搜尋的過程,廣度優先遍歷需要使用一個佇列以保持訪問過的結點的順序,以便按這個順字來訪問這些結點的鄰接結點
廣度優先遍歷演算法步驟
1)訪問初始結點v並標記結點v為已訪問。
2)結點v入佇列
- 當佇列非空時,繼續執行,否則演算法結束。
4)出佇列, 取得隊頭結點u。
5)查詢結點u的第一個鄰接結點w。 - 若結點u的鄰接結點w不存在,則轉到步驟3; 否則迴圈執行以下三個步驟:
6.1若結點w尚未被訪問,則訪問結點w並標記為已訪問。
6.2結點w入佇列
6.3查詢結點u的繼w鄰接結點後的下一個鄰接結點w,轉到步驟6。
package com.xudong.DataStructures;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
public class GraphDemo {
private ArrayList<String> vertexList; //儲存頂點的集合
private int[][] edges;//儲存圖對應的鄰接矩陣
private int numOfEdges;//表示邊的數目
private boolean[] isVisted;//記錄節點是否被訪問
public static void main(String[] args) {
int n = 5;
String Vertexs[] = {"A","B","C","D","E"};
//建立圖物件
GraphDemo graph = new GraphDemo(n);
//新增頂點
for (String vertex : Vertexs){
graph.insertVertex(vertex);
}
//新增邊
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();
System.out.println("深度優先遍歷:");
graph.dfs();
System.out.println();
System.out.println("廣度優先遍歷:");
graph.bfs();
}
//構造器
public GraphDemo(int n){
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
isVisted = new boolean[5];
}
//得到第一個鄰接節點的下標
public int getFirstNeighbor(int index){
for (int j = 0; j < vertexList.size(); j++) {
if (edges[index][j] > 0){
return j;
}
}
return -1;
}
//得到下一個鄰接節點的下標
public int getNextNeighbor(int v1,int v2){
for (int j = v2 + 1; j < vertexList.size(); j++) {
if (edges[v1][j] > 0){
return j;
}
}
return -1;
}
//深度優先遍歷演算法
private void dfs(boolean[] isVisted,int i){
//首先訪問該節點,輸出
System.out.print(getValueByIndex(i) + " -> ");
//將節點設定為已經訪問
isVisted[i] = true;
//查詢節點i的第一個鄰接節點w
int w = getFirstNeighbor(i);
while (w != -1){
if (!isVisted[w]){//如果當前節點沒有被訪問過
dfs(isVisted,w);
}
//如果訪問過
w = getNextNeighbor(i,w);
}
}
//對dfs過載,遍歷所有節點,並進行dfs
public void dfs(){
isVisted = new boolean[vertexList.size()];
//遍歷所有節點,進行回溯
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisted[i]){
dfs(isVisted,i);
}
}
}
//對一個節點進行廣度優先遍歷的方法
private void bfs(boolean[] isVisted,int i){
int u;//表示佇列頭節點對應的下標
int w;//鄰接節點w
//佇列,記錄節點訪問的順序
LinkedList queue = new LinkedList();
//訪問節點,輸出節點資訊
System.out.print(getValueByIndex(i) + " -> ");
//標記為已訪問
isVisted[i] = true;
//將節點加入佇列
queue.addLast(i);
while (!queue.isEmpty()){
//取出佇列頭結點的下標
u = (Integer) queue.removeFirst();
//得到第一個鄰接節點的下標w
w = getFirstNeighbor(u);
while (w != -1){
//是否訪問過
if (!isVisted[w]){
System.out.print(getValueByIndex(w) + " -> ");
//標記為已訪問
isVisted[w] = true;
//入佇列
queue.addLast(w);
}
//以u為前驅節點,找w後面的下一個鄰接節點
w = getNextNeighbor(u,w);//體現出廣度優先
}
}
}
//對bfs過載,遍歷所有節點,並進行bfs
public void bfs(){
isVisted = new boolean[vertexList.size()];
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisted[i]){
bfs(isVisted,i);
}
}
}
//------------常用方法------------------
//返回節點的個數
public int getNumOfVertex(){
return vertexList.size();
}
//顯示圖對應的矩陣
public void showGraph(){
for (int[] link : edges){
System.err.println(Arrays.toString(link));
}
}
//得到邊的數目
public int getNumOfEdges(){
return numOfEdges;
}
//返回節點i的下標
public String getValueByIndex(int i){
return vertexList.get(i);
}
//返回v1和v2的權值
public int getWeight(int v1,int v2){
return edges[v1][v2];
}
//插入節點
public void insertVertex(String vertex){
vertexList.add(vertex);
}
/**新增邊
* @param v1 頂點的下標
* @param v2 第二個頂點的下標
* @param weight 權值
*/
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
}