Java數據結構與算法 day12 圖

第12章 圖

本章源碼:https://github.com/name365/Java-Data-structure

圖的基本介紹和存儲形式

圖基本介紹

爲什麼要有圖???

前面我們學了線性表和樹,線性表侷限於一個直接前驅和一個直接後繼的關係,樹也只能有一個直接前驅也就是父節點。當我們需要表示多對多的關係時,這裏我們就用到了圖。

圖的舉例說明

  • 圖是一種數據結構,其中結點可以具有零個或多個相鄰元素。兩個結點之間的連接稱爲邊。 結點也可以稱爲頂點。如圖:

在這裏插入圖片描述
在這裏插入圖片描述

l圖的常用概念

  • 頂點(vertex)
  • 邊(edge)

在這裏插入圖片描述

  • 路徑:比如從 D -> C 的路徑有
    • D->B->C
    • D->A->B->C
  • 無向圖(上圖):頂點之間的連接沒有方向,比如A-B,即可以是 A-> B 也可以 B->A .
  • 有向圖:頂點之間的連接有方向,比如A-B,只能是 A-> B 不能是 B->A .

在這裏插入圖片描述

  • 帶權圖:這種邊帶權值的圖也叫網.

在這裏插入圖片描述

圖的表示方式

  • 圖的表示方式有兩種:二維數組表示(鄰接矩陣);鏈表表示(鄰接表)。

鄰接矩陣

 鄰接矩陣是表示圖形中頂點之間相鄰關係的矩陣,對於n個頂點的圖而言,矩陣是的row和col表示的是1....n個點。

在這裏插入圖片描述

鄰接表:

1.鄰接矩陣需要爲每個頂點都分配n個邊的空間,其實有很多邊都是不存在,會造成空間的一定損失.
2.鄰接表的實現只關心存在的邊,不關心不存在的邊。因此沒有空間浪費,鄰接表由數組+鏈表組成

在這裏插入圖片描述

說明:
1.標號爲0的結點的相關聯的結點爲 1 2 3 4
2.標號爲1的結點的相關聯結點爲0 43.標號爲2的結點相關聯的結點爲 0 4 5
4.....

圖的創建圖解和代碼實現

要求: 代碼實現如下圖結構.

在這裏插入圖片描述

思路分析  
(1) 存儲頂點String  使用 ArrayList 
(2) 保存矩陣 int[][] edges 

代碼實現如下:

import java.util.ArrayList;
import java.util.Arrays;

public class Graph {

	private ArrayList<String> vertexList; // 存儲頂點集合
	private int[][] str; // 存儲圖對應的鄰接矩陣
	private int numofEdges; // 表示邊的數目

	public static void main(String[] args) {

		// 測試圖的創建
		int n = 5; // 結點的個數
		String Ver[] = { "A", "B", "C", "D", "E" };

		// 創建圖對象
		Graph graph = new Graph(n);
		// 循環的添加頂點
		for (String vertex : Ver) {
			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.showGraph();
		
	}

	// 構造器
	public Graph(int n) {
		// 初始化矩陣和vertexList
		str = new int[n][n];
		vertexList = new ArrayList<String>(n);
		numofEdges = 0;
	}

	// 插入結點
	public void insertVertex(String vertex) {
		vertexList.add(vertex);
	}

	// 添加邊
	/**
	 * 
	 * @Description
	 * @author subei
	 * @date 2020年6月14日下午6:37:03
	 * @param v1
	 *            表示點的下標即第幾個頂點 "A"-"B" "A"->0 "B"->1
	 * @param v2
	 *            第二個頂點對應的下標
	 * @param weight
	 *            表示
	 */
	public void insertEdge(int v1, int v2, int weight) {
		str[v1][v2] = weight;
		str[v2][v1] = weight;
		numofEdges++;
	}

	// 圖中常用的方法
	// 返回結點的個數
	public int getNumber() {
		return vertexList.size();
	}

	// 得到邊的個數
	public int getNumEdg() {
		return numofEdges;
	}

	// 返回結點i(下標)對應的值 0->"A" 1->"B" 2->"C"
	public String getValueByIndex(int i) {
		return vertexList.get(i);
	}

	// 返回v1和v2的權值
	public int getWeight(int v1, int v2) {
		return str[v1][v2];
	}

	// 返回結點的個數
	public int getNumOfVertex() {
		return vertexList.size();
	}

	// 顯示圖對應的矩陣
	public void showGraph() {
		for (int[] link : str) {
			System.err.println(Arrays.toString(link));
		}
	}

}

圖的深度優先(DFS)算法圖解與實現

圖遍歷介紹

所謂圖的遍歷,即是對結點的訪問。一個圖有那麼多個結點,如何遍歷這些結點,需要特定策略,一般有兩種訪問策略: (1) (2)

  • 深度優先遍歷基本思想

    圖的深度優先搜索(Depth First Search)1.深度優先遍歷,從初始訪問結點出發,初始訪問結點可能有多個鄰接結點,深度優先遍歷的策略就是首先訪問第一個鄰接結點,然後再以這個被訪問的鄰接結點作爲初始結點,訪問它的第一個鄰接結點, 可以這樣理解:每次都在訪問完當前結點後首先訪問當前結點的第一個鄰接結點。
    
    2.我們可以看到,這樣的訪問策略是優先往縱向挖掘深入,而不是對一個結點的所有鄰接結點進行橫向訪問。
    
    3.顯然,深度優先搜索是一個遞歸的過程。
    
深度優先遍歷算法步驟:
1.訪問初始結點v,並標記結點v爲已訪問。
2.查找結點v的第一個鄰接結點w。
3.若w存在,則繼續執行4,如果w不存在,則回到第1步,將從v的下一個結點繼續。
4.若w未被訪問,對w進行深度優先遍歷遞歸(即把w當做另一個v,然後進行步驟123)。
5.查找結點v的w鄰接結點的下一個鄰接結點,轉到步驟3

看一個具體案例分析:

要求:對下圖進行深度優先搜索, 從A 開始遍歷。

在這裏插入圖片描述

     A   B   C   D   E
A    0   1   1   0   0
B    1   0   1   1   1
C    1   1   0   0   0
D    0   1   0   0   0 
E    0   1   0   0   0

//說明
//(1) 1 表示能夠直接連接
//(2) 0 表示不能直接連接

代碼實現如下:

import java.util.ArrayList;
import java.util.Arrays;

public class Graph {

	private ArrayList<String> vertexList; // 存儲頂點集合
	private int[][] str; // 存儲圖對應的鄰接矩陣
	private int numofEdges; // 表示邊的數目
	// 定義一個數組boolean[], 記錄某個結點是否被訪問
	private boolean[] isNut;

	public static void main(String[] args) {

		// 測試圖的創建
		int n = 5; // 結點的個數
		String Ver[] = { "A", "B", "C", "D", "E" };

		// 創建圖對象
		Graph graph = new Graph(n);
		// 循環的添加頂點
		for (String vertex : Ver) {
			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.showGraph();

		//測試dfs
		System.out.println("深度遍歷:");
		graph.dfs();	//A->B->C->D->E->
		
	}

	// 構造器
	public Graph(int n) {
		// 初始化矩陣和vertexList
		str = new int[n][n];
		vertexList = new ArrayList<String>(n);
		numofEdges = 0;
		isNut = new boolean[5];
	}

	// 插入結點
	public void insertVertex(String vertex) {
		vertexList.add(vertex);
	}

	// 添加邊
	/**
	 * 
	 * @Description
	 * @author subei
	 * @date 2020年6月14日下午6:37:03
	 * @param v1
	 *            表示點的下標即第幾個頂點 "A"-"B" "A"->0 "B"->1
	 * @param v2
	 *            第二個頂點對應的下標
	 * @param weight
	 *            表示
	 */
	public void insertEdge(int v1, int v2, int weight) {
		str[v1][v2] = weight;
		str[v2][v1] = weight;
		numofEdges++;
	}

	// 圖中常用的方法
	// 返回結點的個數
	public int getNumber() {
		return vertexList.size();
	}

	// 得到邊的個數
	public int getNumEdg() {
		return numofEdges;
	}

	// 返回結點i(下標)對應的值 0->"A" 1->"B" 2->"C"
	public String getValueByIndex(int i) {
		return vertexList.get(i);
	}

	// 返回v1和v2的權值
	public int getWeight(int v1, int v2) {
		return str[v1][v2];
	}

	// 返回結點的個數
	public int getNumOfVertex() {
		return vertexList.size();
	}

	// 顯示圖對應的矩陣
	public void showGraph() {
		for (int[] link : str) {
			System.err.println(Arrays.toString(link));
		}
	}

	// 得到第一個鄰接結點的下標 w
	/**
	 * 
	 * @Description
	 * @author subei
	 * @date 2020年6月14日下午8:03:22
	 * @param index
	 * @return 如果存在就返回對應的下標,否則返回-1
	 */
	public int getFirstNeigh(int index) {
		for (int j = 0; j < vertexList.size(); j++) {
			if (str[index][j] > 0) {
				return j;
			}
		}
		return -1;
	}

	// 根據前一個鄰接結點的下標來獲取下一個鄰接結點
	public int getNextNeigh(int v1, int v2) {
		for (int j = v2 + 1; j < vertexList.size(); j++) {
			if (str[v1][j] > 0) {
				return j;
			}
		}
		return -1;
	}

	// 深度優先算法
	public void dfs(boolean[] isNut, int i) {
		// 首先我們訪問該結點,輸出
		System.out.print(getValueByIndex(i) + "->");
		// 將結點設置爲已經訪問
		isNut[i] = true;
		// 查找結點i的第一個鄰接結點w
		int w = getFirstNeigh(i);
		while (w != -1) {// 說明找到了
			if (!isNut[w]) {
				dfs(isNut, w);
			}
			// 如果w結點已經被訪問過
			w = getNextNeigh(i, w);
		}
	}

	//對DFS進行重載,遍歷所有的結點,並進行DFS
	public void dfs() {
		isNut = new boolean[vertexList.size()];
		//遍歷所有的結點,進行dfs[回溯]
		for(int i = 0; i < getNumOfVertex(); i++) {
			if(!isNut[i]) {
				dfs(isNut, i);
			}
		}
	}
	
}

圖的廣度優先(BFS)算法圖解

廣度優先遍歷基本思想

圖的廣度優先搜索(Broad First Search) 。

類似於一個分層搜索的過程,廣度優先遍歷需要使用一個隊列以保持訪問過的結點的順序,以便按這個順序來訪問這些結點的鄰接結點

廣度優先遍歷算法步驟:
1.訪問初始結點v並標記結點v爲已訪問。
2.結點v入隊列
3.當隊列非空時,繼續執行,否則算法結束。
4.出隊列,取得隊頭結點u。
5.查找結點u的第一個鄰接結點w。
6.若結點u的鄰接結點w不存在,則轉到步驟3;否則循環執行以下三個步驟:
	6.1 若結點w尚未被訪問,則訪問結點w並標記爲已訪問。 
	6.2 結點w入隊列 
	6.3 查找結點u的繼w鄰接結點後的下一個鄰接結點w,轉到步驟6

在這裏插入圖片描述

     A   B   C   D   E
A    0   1   1   0   0
B    1   0   1   1   1
C    1   1   0   0   0
D    0   1   0   0   0 
E    0   1   0   0   0

//說明
//(1) 1 表示能夠直接連接
//(2) 0 表示不能直接連接

代碼實現如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

public class Graph {

	private ArrayList<String> vertexList; // 存儲頂點集合
	private int[][] str; // 存儲圖對應的鄰接矩陣
	private int numofEdges; // 表示邊的數目
	// 定義一個數組boolean[], 記錄某個結點是否被訪問
	private boolean[] isNut;

	public static void main(String[] args) {

		// 測試圖的創建
		int n = 5; // 結點的個數
		String Ver[] = { "A", "B", "C", "D", "E" };

		// 創建圖對象
		Graph graph = new Graph(n);
		// 循環的添加頂點
		for (String vertex : Ver) {
			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.showGraph();

		//測試bfs
		System.out.println("廣度遍歷:");
		graph.bfs();	//A=>B=>C=>D=>E=>
		
	}

	// 構造器
	public Graph(int n) {
		// 初始化矩陣和vertexList
		str = new int[n][n];
		vertexList = new ArrayList<String>(n);
		numofEdges = 0;
		isNut = new boolean[5];
	}

	// 插入結點
	public void insertVertex(String vertex) {
		vertexList.add(vertex);
	}

	// 添加邊
	/**
	 * 
	 * @Description
	 * @author subei
	 * @date 2020年6月14日下午6:37:03
	 * @param v1
	 *            表示點的下標即第幾個頂點 "A"-"B" "A"->0 "B"->1
	 * @param v2
	 *            第二個頂點對應的下標
	 * @param weight
	 *            表示
	 */
	public void insertEdge(int v1, int v2, int weight) {
		str[v1][v2] = weight;
		str[v2][v1] = weight;
		numofEdges++;
	}

	// 圖中常用的方法
	// 返回結點的個數
	public int getNumber() {
		return vertexList.size();
	}

	// 得到邊的個數
	public int getNumEdg() {
		return numofEdges;
	}

	// 返回結點i(下標)對應的值 0->"A" 1->"B" 2->"C"
	public String getValueByIndex(int i) {
		return vertexList.get(i);
	}

	// 返回v1和v2的權值
	public int getWeight(int v1, int v2) {
		return str[v1][v2];
	}

	// 返回結點的個數
	public int getNumOfVertex() {
		return vertexList.size();
	}

	// 顯示圖對應的矩陣
	public void showGraph() {
		for (int[] link : str) {
			System.err.println(Arrays.toString(link));
		}
	}

	// 得到第一個鄰接結點的下標 w
	/**
	 * 
	 * @Description
	 * @author subei
	 * @date 2020年6月14日下午8:03:22
	 * @param index
	 * @return 如果存在就返回對應的下標,否則返回-1
	 */
	public int getFirstNeigh(int index) {
		for (int j = 0; j < vertexList.size(); j++) {
			if (str[index][j] > 0) {
				return j;
			}
		}
		return -1;
	}

	// 根據前一個鄰接結點的下標來獲取下一個鄰接結點
	public int getNextNeigh(int v1, int v2) {
		for (int j = v2 + 1; j < vertexList.size(); j++) {
			if (str[v1][j] > 0) {
				return j;
			}
		}
		return -1;
	}

	// 廣度優先算法
	public void bfs(boolean[] isNut, int i) {
		int u ; // 表示隊列的頭結點對應下標
		int w ; // 鄰接結點w
		//隊列,記錄結點訪問的順序
		LinkedList<Integer> queue = new LinkedList<Integer>();
		// 訪問結點,輸出結點信息
		System.out.print(getValueByIndex(i) + "=>");
		// 標記爲已訪問
		isNut[i] = true;
		// 將結點加入隊列
		queue.addLast(i);
		while( !queue.isEmpty()) {
			// 取出隊列的頭結點下標
			u = (Integer)queue.removeFirst();
			// 得到第一個鄰接結點的下標 w 
			w = getFirstNeigh(u);
			while(w != -1) {//找到了
				// 是否訪問過
				if(!isNut[w]) {
					System.out.print(getValueByIndex(w) + "=>");
					// 標記已經訪問
					isNut[w] = true;
					// 入隊
					queue.addLast(w);
				}
				// 以u爲前驅點,找w後面的下一個鄰結點
				w = getNextNeigh(u, w); // 體現廣度優先
			}
		}
	}

	//對BFS進行重載,遍歷所有的結點,並進行BFS
	public void bfs() {
		isNut = new boolean[vertexList.size()];
		for(int i = 0; i < getNumOfVertex(); i++) {
			if(!isNut[i]) {
				bfs(isNut, i);
			}
		}
	}	
}

圖的DFS和BFS比較

在這裏插入圖片描述

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);

深度優先遍歷順序爲 1->2->4->8->5->3->6->7
廣度優先算法的遍歷順序爲:1->2->3->4->5->6->7->8 

代碼實現如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

public class Graph {

	private ArrayList<String> vertexList; // 存儲頂點集合
	private int[][] str; // 存儲圖對應的鄰接矩陣
	private int numofEdges; // 表示邊的數目
	// 定義一個數組boolean[], 記錄某個結點是否被訪問
	private boolean[] isNut;

	public static void main(String[] args) {

		// 測試圖的創建
		int n = 8; // 結點的個數
		String Ver[] = {"1", "2", "3", "4", "5", "6", "7", "8"};

		// 創建圖對象
		Graph graph = new Graph(n);
		// 循環的添加頂點
		for (String vertex : Ver) {
			graph.insertVertex(vertex);
		}

		// 添加邊
		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
		System.out.println("深度遍歷:");
		graph.dfs(); // 1->2->4->8->5->3->6->7->

		// 測試bfs
		System.out.println("廣度遍歷:");
		graph.bfs(); // 1=>2=>3=>4=>5=>6=>7=>8=>

	}

	// 構造器
	public Graph(int n) {
		// 初始化矩陣和vertexList
		str = new int[n][n];
		vertexList = new ArrayList<String>(n);
		numofEdges = 0;
		isNut = new boolean[5];
	}

	// 插入結點
	public void insertVertex(String vertex) {
		vertexList.add(vertex);
	}

	// 添加邊
	/**
	 * 
	 * @Description
	 * @author subei
	 * @date 2020年6月14日下午6:37:03
	 * @param v1
	 *            表示點的下標即第幾個頂點 "A"-"B" "A"->0 "B"->1
	 * @param v2
	 *            第二個頂點對應的下標
	 * @param weight
	 *            表示
	 */
	public void insertEdge(int v1, int v2, int weight) {
		str[v1][v2] = weight;
		str[v2][v1] = weight;
		numofEdges++;
	}

	// 圖中常用的方法
	// 返回結點的個數
	public int getNumber() {
		return vertexList.size();
	}

	// 得到邊的個數
	public int getNumEdg() {
		return numofEdges;
	}

	// 返回結點i(下標)對應的值 0->"A" 1->"B" 2->"C"
	public String getValueByIndex(int i) {
		return vertexList.get(i);
	}

	// 返回v1和v2的權值
	public int getWeight(int v1, int v2) {
		return str[v1][v2];
	}

	// 返回結點的個數
	public int getNumOfVertex() {
		return vertexList.size();
	}

	// 顯示圖對應的矩陣
	public void showGraph() {
		for (int[] link : str) {
			System.err.println(Arrays.toString(link));
		}
	}

	// 得到第一個鄰接結點的下標 w
	/**
	 * 
	 * @Description
	 * @author subei
	 * @date 2020年6月14日下午8:03:22
	 * @param index
	 * @return 如果存在就返回對應的下標,否則返回-1
	 */
	public int getFirstNeigh(int index) {
		for (int j = 0; j < vertexList.size(); j++) {
			if (str[index][j] > 0) {
				return j;
			}
		}
		return -1;
	}

	// 根據前一個鄰接結點的下標來獲取下一個鄰接結點
	public int getNextNeigh(int v1, int v2) {
		for (int j = v2 + 1; j < vertexList.size(); j++) {
			if (str[v1][j] > 0) {
				return j;
			}
		}
		return -1;
	}

	// 深度優先算法
	public void dfs(boolean[] isNut, int i) {
		// 首先我們訪問該結點,輸出
		System.out.print(getValueByIndex(i) + "->");
		// 將結點設置爲已經訪問
		isNut[i] = true;
		// 查找結點i的第一個鄰接結點w
		int w = getFirstNeigh(i);
		while (w != -1) {// 說明找到了
			if (!isNut[w]) {
				dfs(isNut, w);
			}
			// 如果w結點已經被訪問過
			w = getNextNeigh(i, w);
		}
	}

	// 對DFS進行重載,遍歷所有的結點,並進行DFS
	public void dfs() {
		isNut = new boolean[vertexList.size()];
		// 遍歷所有的結點,進行dfs[回溯]
		for (int i = 0; i < getNumOfVertex(); i++) {
			if (!isNut[i]) {
				dfs(isNut, i);
			}
		}
	}

	// 廣度優先算法
	public void bfs(boolean[] isNut, int i) {
		int u; // 表示隊列的頭結點對應下標
		int w; // 鄰接結點w
		// 隊列,記錄結點訪問的順序
		LinkedList<Integer> queue = new LinkedList<Integer>();
		// 訪問結點,輸出結點信息
		System.out.print(getValueByIndex(i) + "=>");
		// 標記爲已訪問
		isNut[i] = true;
		// 將結點加入隊列
		queue.addLast(i);
		while (!queue.isEmpty()) {
			// 取出隊列的頭結點下標
			u = (Integer) queue.removeFirst();
			// 得到第一個鄰接結點的下標 w
			w = getFirstNeigh(u);
			while (w != -1) {// 找到了
				// 是否訪問過
				if (!isNut[w]) {
					System.out.print(getValueByIndex(w) + "=>");
					// 標記已經訪問
					isNut[w] = true;
					// 入隊
					queue.addLast(w);
				}
				// 以u爲前驅點,找w後面的下一個鄰結點
				w = getNextNeigh(u, w); // 體現廣度優先
			}
		}
	}

	// 對BFS進行重載,遍歷所有的結點,並進行BFS
	public void bfs() {
		isNut = new boolean[vertexList.size()];
		for (int i = 0; i < getNumOfVertex(); i++) {
			if (!isNut[i]) {
				bfs(isNut, i);
			}
		}
	}
}

本章思維導圖

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章