Java程式設計:圖
為什麼要有圖
- 前面我們學了線性表和樹
- 線性表侷限於一個直接前驅和一個直接後繼的關係
- 樹也只能有一個直接前驅也就是父節點
- 當我們需要表示多對多的關係時, 這裡我們就用到了圖
圖的舉例說明
圖是一種資料結構,其中結點可以具有零個或多個相鄰元素。兩個結點之間的連線稱為邊。 結點也可以稱為頂點。如圖:
圖的常用概念
- 頂點(vertex)
- 邊(edge)
- 路徑
- 無向圖
- 有向圖
- 帶權圖
圖的表示方式
圖的表示方式有兩種:二維陣列表示(鄰接矩陣);連結串列表示(鄰接表)。
- 鄰接矩陣
鄰接矩陣是表示圖形中頂點之間相鄰關係的矩陣,對於n個頂點的圖而言,矩陣是的row和col表示的是1…n個點。
- 鄰接表
- 鄰接矩陣需要為每個頂點都分配n個邊的空間,其實有很多邊都是不存在,會造成空間的一定損失.
- 鄰接表的實現只關心存在的邊,不關心不存在的邊。因此沒有空間浪費,鄰接表由陣列+連結串列組成
圖的快速入門案例
要求: 程式碼實現如下圖結構.
思路分析 (1) 儲存頂點String 使用 ArrayList (2) 儲存矩陣 int[][] edges
程式碼實現
/**
*插入結點(頂點)
*
* @param vertex 結點
*/
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
/**
* 新增邊
*
* @param v1 表示點的下標 即是第幾個頂點 "A" - "B" => "A" - 0 "B" - 1
* @param v2 第二個頂點對應的下標
* @param weight 表示兩個點是否有關聯
*/
public void insertEdge(int v1, int v2, int weight) {
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
圖的深度優先遍歷介紹
圖遍歷介紹
所謂圖的遍歷,即是對結點的訪問。一個圖有那麼多個結點,如何遍歷這些結點,需要特定策略,一般有兩種訪問策略:
- 深度優先遍歷
- 廣度優先遍歷
深度優先遍歷基本思想
圖的深度優先搜尋(Depth First Search) 。
- 深度優先遍歷,從初始訪問結點出發,初始訪問結點可能有多個鄰接結點,深度優先遍歷的策略就是首先訪問第一個鄰接結點,然後再以這個被訪問的鄰接結點作為初始結點,訪問它的第一個鄰接結點, 可以這樣理解:每次都在訪問完當前結點後首先訪問當前結點的第一個鄰接結點。
- 我們可以看到,這樣的訪問策略是優先往縱向挖掘深入,而不是對一個結點的所有鄰接結點進行橫向訪問。
- 顯然,深度優先搜尋是一個遞迴的過程
深度優先遍歷演算法步驟
- 訪問初始結點v,並標記結點v為已訪問。
- 查詢結點v的第一個鄰接結點w。
- 若w存在,則繼續執行4,如果w不存在,則回到第1步,將從v的下一個結點繼續。
- 若w未被訪問,對w進行深度優先遍歷遞迴(即把w當做另一個v,然後進行步驟123)。
- 找結點v的w鄰接結點的下一個鄰接結點,轉到步驟3。
看一個具體案例分析:
要求:對下圖進行深度優先搜尋, 從A 開始遍歷.
思路分析
程式碼實現
/**
* 對一個結點進行深度優先遍歷演算法
*
* @param isVisited 是否訪問表示矩陣
* @param i 第i個結點 第一次是0
*/
private void dfs(boolean[] isVisited, int i) {
// 首先訪問該節點
System.out.print(getValueByIndex(i) + "->");
// 將該節點設定為已經訪問
isVisited[i] = true;
// 查詢結點i的第一個鄰接節點
int w = getFirstNeighbor(i);
while (w != -1) {//說明有
if (!isVisited[w]) {
dfs(isVisited, w);
}
// 如果w存在,但是已經被訪問過
w = getNextNeighbor(i, w);
}
}
// 對dfs過載 遍歷所有結點,並進行dfs
public void dfs() {
isVisited = new boolean[vertexList.size()];
// 遍歷所有的結點,進行dfs(回溯
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]) {
dfs(isVisited, i);
}
}
}
廣度優先遍歷基本思想
圖的廣度優先搜尋(Broad First Search) 。
類似於一個分層搜尋的過程,廣度優先遍歷需要使用一個佇列以保持訪問過的結點的順序,以便按這個順序來訪問這些結點的鄰接結點
廣度優先遍歷演算法步驟
- 訪問初始結點v並標記結點v為已訪問。
- 結點v入佇列
- 當佇列非空時,繼續執行,否則演算法結束。
- 出佇列,取得隊頭結點u。
- 查詢結點u的第一個鄰接結點w。
- 若結點u的鄰接結點w不存在,則轉到步驟3;否則迴圈執行以下三個步驟:
6.1 若結點w尚未被訪問,則訪問結點w並標記為已訪問。
6.2 結點w入佇列
6.3 查詢結點u的繼w鄰接結點後的下一個鄰接結點w,轉到步驟6。
廣度優先舉例說明
廣度優先程式碼實現
/**
* 對一個結點進行廣度優先遍歷演算法
*
* @param isVisited 是否訪問表示矩陣
* @param i 第i個結點 第一次是0
*/
private void bfs(boolean[] isVisited, int i) {
int u; // 表示佇列頭結點對應下標
int w; // 表示鄰接節點w
// 佇列,記錄結點訪問的順序
LinkedList<Integer> queue = new LinkedList<>();
// 訪問結點,輸出資訊
System.out.print(getValueByIndex(i) + "=>");
// 標記為已訪問
isVisited[i] = true;
// 將節點加入佇列
queue.addLast(i);
while (!queue.isEmpty()) {
// 出去佇列頭結點下標
u = queue.removeFirst();
// 得到第一個臨界點下標w
w = getFirstNeighbor(u);
while (w != -1) { // 找到了
// 判斷是否訪問
if (!isVisited[w]) {
System.out.print(getValueByIndex(w) + "=>");
// 標記已訪問
isVisited[w] = true;
// 入隊
queue.addFirst(w);
}
// 以u為前驅結點,找w後面的下一個鄰接點
w = getNextNeighbor(u, w); // 體現出廣度優先
}
}
}
// 對dfs過載 遍歷所有結點,並進行dfs
public void bfs() {
isVisited = new boolean[vertexList.size()];
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]) {
bfs(isVisited, i);
}
}
}
應用例項
圖的完整程式碼
package graph;
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[] Vertexes = {"A", "B", "C", "D", "E"};
String[] Vertexes = {"1", "2", "3", "4", "5", "6", "7", "8"};
// 建立圖物件
Graph graph = new Graph(8);
// 迴圈新增頂點
for (String Vertex : Vertexes) {
graph.insertVertex(Vertex);
}
// 新增邊
// A-B A-C B-C B-D B-E
// graph.insertEdge(0, 1, 1); // A-B
// graph.insertEdge(0, 2, 1); // A-C
// graph.insertEdge(1, 2, 1); // B-C
// graph.insertEdge(1, 3, 1); // B-D
// graph.insertEdge(1, 4, 1); // B-E
graph.insertEdge(0, 1, 1);
graph.insertEdge(0, 2, 1);
graph.insertEdge(1, 3, 1);
graph.insertEdge(1, 4, 1);
graph.insertEdge(3, 7, 1);
graph.insertEdge(4, 7, 1);
graph.insertEdge(2, 5, 1);
graph.insertEdge(2, 6, 1);
graph.insertEdge(5, 6, 1);
graph.showGraph();
// 測試dfs遍歷是否ok
System.out.println("深度優先遍歷");
graph.dfs();
System.out.println();
// 測試bfs遍歷是否ok
System.out.println("廣度優先遍歷");
graph.bfs();
}
/**
* 構造器
*
* @param n 圖的結點數量
*/
public Graph(int n) {
// 初始化矩陣和ArrayList
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
// isVisited = new boolean[5];
}
/**
* 插入結點(頂點)
*
* @param vertex 結點
*/
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
/**
* 新增邊
*
* @param v1 表示點的下標 即是第幾個頂點 "A" - "B" => "A" - 0 "B" - 1
* @param v2 第二個頂點對應的下標
* @param weight 表示兩個點是否有關聯
*/
public void insertEdge(int v1, int v2, int weight) {
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
/**
* 得到第index個結點的第一個鄰接節點的下標w
*
* @param index 想尋求鄰接節點的結點
* @return 找到返回下標,找不到返回-1
*/
public int getFirstNeighbor(int index) {
for (int i = 0; i < vertexList.size(); i++) {
if (edges[index][i] > 0) {
return i;
}
}
return -1;
}
/**
* 根據前一個鄰接節點的下標獲取下一個鄰接節點
*
* @param v1 結點橫座標
* @param v2 結點縱座標
* @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;
}
/**
* 對一個結點進行廣度優先遍歷演算法
*
* @param isVisited 是否訪問表示矩陣
* @param i 第i個結點 第一次是0
*/
private void bfs(boolean[] isVisited, int i) {
int u; // 表示佇列頭結點對應下標
int w; // 表示鄰接節點w
// 佇列,記錄結點訪問的順序
LinkedList<Integer> queue = new LinkedList<>();
// 訪問結點,輸出資訊
System.out.print(getValueByIndex(i) + "=>");
// 標記為已訪問
isVisited[i] = true;
// 將節點加入佇列
queue.addLast(i);
while (!queue.isEmpty()) {
// 出去佇列頭結點下標
u = queue.removeFirst();
// 得到第一個臨界點下標w
w = getFirstNeighbor(u);
while (w != -1) { // 找到了
// 判斷是否訪問
if (!isVisited[w]) {
System.out.print(getValueByIndex(w) + "=>");
// 標記已訪問
isVisited[w] = true;
// 入隊
queue.addFirst(w);
}
// 以u為前驅結點,找w後面的下一個鄰接點
w = getNextNeighbor(u, w); // 體現出廣度優先
}
}
}
// 對dfs過載 遍歷所有結點,並進行dfs
public void bfs() {
isVisited = new boolean[vertexList.size()];
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]) {
bfs(isVisited, i);
}
}
}
/**
* 對一個結點進行深度優先遍歷演算法
*
* @param isVisited 是否訪問表示矩陣
* @param i 第i個結點 第一次是0
*/
private void dfs(boolean[] isVisited, int i) {
// 首先訪問該節點
System.out.print(getValueByIndex(i) + "->");
// 將該節點設定為已經訪問
isVisited[i] = true;
// 查詢結點i的第一個鄰接節點
int w = getFirstNeighbor(i);
while (w != -1) {//說明有
if (!isVisited[w]) {
dfs(isVisited, w);
}
// 如果w存在,但是已經被訪問過
w = getNextNeighbor(i, w);
}
}
// 對dfs過載 遍歷所有結點,並進行dfs
public void dfs() {
isVisited = new boolean[vertexList.size()];
// 遍歷所有的結點,進行dfs(回溯
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]) {
dfs(isVisited, i);
}
}
}
// 圖中常用的方法
/**
* 返回結點的個數
*
* @return 結點個數
*/
public int getNumOfVertex() {
return vertexList.size();
}
/**
* 返回邊的個數
*
* @return 邊的個數
*/
public int getNumOfEdges() {
return numOfEdges;
}
/**
* 返回結點i(下標) 對應的資料 0->"A" 1->"B" 2->"C"
*
* @param i 第i個
* @return 第i個資料
*/
public String getValueByIndex(int i) {
return vertexList.get(i);
}
/**
* 返回兩個結點間的權值
*
* @param v1 結點1
* @param v2 結點2
* @return 兩個結點間的權值
*/
public int getWeight(int v1, int v2) {
return edges[v1][v2];
}
/**
* 顯示圖對應的矩陣
*/
public void showGraph() {
// for (int i = 0; i < edges.length; i++) {
// for (int j = 0; j < edges[0].length; j++) {
// System.out.print(edges[i][j] + " ");
// }
// System.out.println();
// }
for (int[] link : edges) {
System.out.println(Arrays.toString(link));
}
}
}
相關文章
- 【java學習】GUI 圖形程式設計JavaGUI程式設計
- 程式設計字典-Java(JavaEE)學習線路圖程式設計Java
- Java 網路程式設計(TCP程式設計 和 UDP程式設計)Java程式設計TCPUDP
- 寫給Java程式設計師學習路線圖Java程式設計師
- Java-GUI程式設計之處理點陣圖JavaGUI程式設計
- 好程式設計師Java培訓分享Java程式設計技巧程式設計師Java
- Java程式設計技巧Java程式設計
- Java Socket程式設計Java程式設計
- 前言-Java程式設計Java程式設計
- Java Socket程式設計Java程式設計
- Java介面程式設計Java程式設計
- JAVA網路程式設計(2)TCP程式設計Java程式設計TCP
- Java程式設計師必備的一些流程圖Java程式設計師流程圖
- Java程式設計開發之資料圖表分析模型Java程式設計模型
- 好程式設計師:Java程式設計師面試秘籍程式設計師Java面試
- 五種Java程式設計高效程式設計方法 - BablaJava程式設計
- Java 網路程式設計 —— 非阻塞式程式設計Java程式設計
- Java程式設計師學習Rust程式設計 - infoworldJava程式設計師Rust
- 好程式設計師Java教程分享Java之設計模式程式設計師Java設計模式
- Java設計模式(一):設計模式概述、UML圖、設計原則Java設計模式
- Java 程式設計開發Java程式設計
- java 併發程式設計Java程式設計
- Java併發程式設計Java程式設計
- Java 網路程式設計Java程式設計
- java 泛型程式設計Java泛型程式設計
- Java程式設計基礎Java程式設計
- Java Socket 程式設計指南Java程式設計
- ●招聘● Java程式設計師Java程式設計師
- JAVA程式設計規則:Java程式設計
- Java遊戲程式設計初步Java遊戲程式設計
- JAVA程式設計師之路Java程式設計師
- java程式設計規範Java程式設計
- Java套接字程式設計Java程式設計
- Java程式設計規則Java程式設計
- java程式設計環境Java程式設計
- JAVA網路程式設計Java程式設計
- java非同步程式設計Java非同步程式設計
- Java NIO程式設計示例Java程式設計