数据结构(十):图
一、 圖概述
日常生活中使用的地圖導航,每個城市看做一個頂點,城市與城市間連通的線路看做聯通的邊,就組成了圖。除了導航,迷宮,電路板等等也是圖,需要用圖這種數據結構去解決很多連通問題。
二、 圖的特性
圖的定義:圖是有一組頂點和一組能將頂點相連的邊組成的數據結構
特殊的圖:
平行邊:連接同一對頂點的多條邊
自環:一條出口和入口都來自同一個頂點的邊
圖的分類:
無向圖:連接頂點的邊是無方向無意義的圖
有向圖:連接頂點的邊具有方向的圖
相鄰頂點:兩個頂點通過一條邊相連時為相鄰頂點
度:頂點邊的數量
子圖:圖所有邊和相連頂點的子集
路徑:A頂點到B頂點若干順序連接的邊
環:起點和終點相同,并且至少含有一條邊的路徑
連通圖:圖中每一個頂點都存在一條邊到達另外一個頂點,稱為連通圖
連通子圖:一幅非連通圖由若干連通部分組成,該連通部分稱為連通子圖
三、 圖的實現
3.1 鄰接矩陣
圖的數據結構可以通過二維數組來表示,即鄰接矩陣,如下圖生成一個8*8的二維數組,通過graph[7][8]=1和graph[8][7]=1來表示頂點7和8的連通。
但該方式存在著內存空間使用率低的問題,N個頂點圖中,得初始化N^2規模的二維數組,空間復雜度是不可接受的。
3.2鄰接表
使用一個頂點數量相同大小的數組Queue[],索引作為圖的頂點,索引對應的隊列存放與頂點相連通的其他頂點,用這種方式來表示圖稱為鄰接表。鄰接表相比鄰接矩陣可以大大的節省內存空間。
圖的鄰接表實現:
|
/** * 圖的實現 * @author jiyukai */ public class Graph { //定點個數 public int V; //邊的數量 public int E; //圖的鄰接表 public Queue<Integer>[] qTable; public Graph(int v) { this.V = v; this.E = 0; //初始化鄰接表,數組中的索引為頂點,值為已隊列,存放相鄰的頂點 qTable = new Queue[v]; for(int i=0;i<v;i++) { qTable[i] = new Queue<Integer>(); } } /** * 向圖中添加一條邊 * @param v * @param w */ public void addEdge(int v,int w) { //頂點v添加w的指向 qTable[v].enqueue(w); //頂點w添加v的指向 qTable[w].enqueue(v); //邊加1 E++; } /** * 返回當前頂點的數量 * @return */ public int V() { return V; } /** * 返回當前邊的數量 * @return */ public int E() { return E; } /** * 獲取與頂點V相鄰的頂點 * @param V * @return */ public Queue adjoin(int V) { return qTable[V]; } } |
四、 深度優先搜索
圖的搜索算法中,深度優先搜索指的是搜索到的頂點既有子結點又有兄弟結點,優先搜索子結點。如下演示為圖的深度優先搜索順序
為了不對已搜索過的頂點重復搜索,我們需要有個布爾類型的數組來標記頂點是否被搜索過,搜索過的就標記為true,不再進行深度搜索,提高效率的同時,該數組也能判斷頂點A到B是否相通
因為相通的代表搜索過,會被標記為true。
|
/** * 深度優先搜索 * @author jiyukai */ public class DepthSearch { //標記頂點x是否有被搜索過 public boolean[] flags; public int count; /** * 深度優先搜索,找出與頂點V想通的所有頂點 * @param G * @param V */ public DepthSearch(Graph G,int V) { //長度置0 this.count = 0; //創建一個與頂點數量相同的標記數組,標記每個頂點是否被搜索過 flags = new boolean[G.V()]; //深度優先搜索 dfs(G, V); } /** * 深度優先搜索實現 * @param G * @param V */ public void dfs(Graph G,int V) { //被搜的頂點V標記為搜索過 flags[V] = true; for(int w : G.qTable[V]) { if(!flags[w]) { System.out.println("搜索的頂點:"+w); dfs(G, w); } } //相通的點+1 count++; } /** * 返回與頂點V相通的所有頂點 * @return */ public int count() { return count; } /** * 判斷頂點w是否與v相通 * @param w * @return */ public boolean isConnected(int w) { return flags[w]; } } |
五、 廣度優先搜索
圖的搜索算法中,廣度優先搜索指的是搜索到的頂點既有子結點又有兄弟結點,優先搜索兄弟結點。如下演示為圖的廣度優先搜索順序
為了不對已搜索過的頂點重復搜索,我們需要有個布爾類型的數組來標記頂點是否被搜索過,搜索過的就標記為true,不再進行廣度搜索,提高效率的同時,該數組也能判斷頂點A到B是否相通
因為相通的代表搜索過,會被標記為true。
|
/** * 廣度優先搜索 * * @author jiyukai */ public class WeightSearch { // 標記頂點x是否有被搜索過 public boolean[] flags; // 聯通的點數量 public int count; // 用來存儲待搜索鄰接表的點 private Queue<Integer> waitSearch; /** * 深度優先搜索,找出與頂點V想通的所有頂點 * * @param G * @param V */ public WeightSearch(Graph G, int V) { // 長度置0 this.count = 0; // 創建一個與頂點數量相同的標記數組,標記每個頂點是否被搜索過 flags = new boolean[G.V()]; // 初始化待搜索頂點隊列 waitSearch = new Queue<>(); // 深度優先搜索 wfs(G, V); } /** * 深度優先搜索實現 * * @param G * @param V */ public void wfs(Graph G, int V) { // 被搜的頂點V標記為搜索過 flags[V] = true; // 將待搜索的元素入隊列 for (int w : G.qTable[V]) { waitSearch.enqueue(w); } while (!waitSearch.isEmpty()) { int searchKey = waitSearch.dequeue(); if (!flags[searchKey]) { wfs(G, searchKey); } } // 相通的點+1 count++; } /** * 返回與頂點V相通的所有頂點 * * @return */ public int count() { return count; } /** * 判斷頂點w是否與v相通 * * @param w * @return */ public boolean isConnected(int w) { return flags[w]; } } |
六、 路徑查找
導航中常用的一個場景就是起點s到重點v之間是否存在一條可連通的路徑,若存在,需要走什么樣的路到達。
基于深度搜索來實現圖的搜索,新增一個數組conn[]來記錄各個連通點之間的關系,索引為頂點,值為指向頂點的相鄰頂點,假設0為起點,在conn數組構建完畢后,我們通過索引0找到頂點4
在通過索引4找到頂點5,依次找下去,并將找到的頂點依次入棧,則最后能找到和0相通的其他頂點和中間經過的路徑。
將找到的頂點依次入棧
|
/** * 基于深度優先搜索實現的路徑查找 * @author jiyukai */ public class DepthSearchPath { // 標記頂點x是否有被搜索過 public boolean[] flags; // 聯通的點數量 public int count; // 初始化起點 public int s; // 連通頂點的關系 public int conn[]; /** * 深度優先搜索,找出與頂點V想通的所有頂點 * @param G * @param V */ public DepthSearchPath(Graph G, int s) { // 長度置0 this.count = 0; // 創建一個與頂點數量相同的標記數組,標記每個頂點是否被搜索過 flags = new boolean[G.V()]; // 初始化連通頂點的關系數組 conn = new int[G.V()]; // 初始化起點 this.s = s; // 深度優先搜索 wfs(G, s); } /** * 深度優先搜索實現 * @param G * @param V */ public void wfs(Graph G, int v) { // 被搜的頂點V標記為搜索過 flags[v] = true; for (int w : G.qTable[v]) { if (!flags[w]) { conn[w] = v; wfs(G, w); } } // 相通的點+1 count++; } /** * 返回與頂點V相通的所有頂點 * @return */ public int count() { return count; } /** * 判斷頂點w是否與v相通 * @param w * @return */ public boolean isConnected(int w) { return flags[w]; } /** * 找出s到頂點v的路徑 * @param v * @return */ public Stack<Integer> pathTo(int v){ //判斷是否連接 if(!isConnected(v)) { return null; } //創建存放連通圖的棧 Stack<Integer> stack = new Stack<Integer>(); //找出與起點s連通路徑上的頂點 for(int x=v;x!=s;x=conn[x]) { stack.push(x); } //起點入棧 stack.push(s); return stack; } } |
總結
- 上一篇: 经典的卷积神经网络简介
- 下一篇: iOS开发的学习笔记