本文最后更新于:July 27, 2024 8:58 PM
这是在看b站上的韩顺平老师的数据结构与算法时做的笔记。等我把后面的都更新完了以后会陆续把前面的补上。
一、基本介绍 为什么要有图
前面我们学了线性表和树
线性表局限于一个前驱和一个后继的关系
树也只能有一个直接前驱也就是父结点
当我们需要表示多对多的关系时,这里我们就用到了图 图是一种数据结构,其中结点可以具有零个或者多个相邻元素。两个结点直接的链接称为边。结点也可以称为顶点。如图:
二、常用概念 1) 顶点|vertex
2) 边|edge
3) 路径|path
4)无向图|Undirected Graph(下图)
无向图:顶点之间的链接没有方向,比如A-B,既可以是A→B,也可以是B←A。
路径:比如D→C的路径有
D→B→C
D→A→B→C
5) 有向图|Directed Graph(下图) 有向图:顶点之间的链接有方向,比如A-B,只能是A→B,不能是B←A。
6)带权图|Weighted Graph(下图) 带权图:这种边带权值的图也叫网
三、表示方式 图的表示方式有两种:二维数组表示(邻接矩阵);链表表示(邻接表)。
1. 邻接矩阵 邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵的row和col表示的是1…n个点。
2. 邻接表 1)邻接矩阵需要为每个顶点都分配n个边的空间,其实有很多边都是不存在,会造成空间的一定损失。
2)邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成。
说明:
标号为0的结点的相关联的结点为 1 2 3 4
标号为1的结点的相关联结点为0 4,
标号为2的结点相关联的结点为 0 4 5
….
四、快速入门案例 1. 要求 代码实现如下图结构。
1 2 3 4 5 6 7 8 9 10 11 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表示不能直接链接
2. 思路分析
存储顶点 String 使用 ArrayList
保存 int[][] edges
3.代码实现 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import java.util.ArrayList;import java.util.Arrays;public class Graph { private ArrayList<String > vertexList; private int [][] edges; private int numOfEdges; public static void main (String [] args) { int n = 5 ; String vertices[] = { "A" , "B" , "C" , "D" , "E" }; Graph graph = new Graph(n); for (String vertex : vertices) { 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(); } public Graph (int n) { edges = new int [n][n]; vertexList = new ArrayList<String >(n); numOfEdges = 0 ; } public void showGraph () { for (int [] link : edges) { System.out.println (Arrays.toString(link)); } } public int getNumOfVertex () { return vertexList.size (); } public int getNumOfEdges () { return numOfEdges; } public String getValueByIndex (int i) { return vertexList.get (i); } public int getWeight (int v1, int v2) { return edges[v1][v2]; } public void insertVertex (String vertex) { vertexList.add(vertex); } public void insertEdge (int v1, int v2, int weight) { edges[v1][v2] = weight; edges[v2][v1] = weight; } }
运行结果 1 2 3 4 5 [0 , 1 , 1 , 0 , 0 ] [1 , 0 , 1 , 1 , 1 ] [1 , 1 , 0 , 0 , 0 ] [0 , 1 , 0 , 0 , 0 ] [0 , 1 , 0 , 0 , 0 ]
五、图的遍历 所谓图的遍历,既是对结点的访问。一个图有那么多结点,如何遍历这些结点,需要特定策略,一般有两种访问策略:
深度优先遍历|Depth First Search
广度优先遍历|Broad First Search
深度优先遍历|DFS 1. 基本思想 图的深度优先搜索
深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点, 可以这样理解:每次都在访问完当前结点 后首先访问当前结点的第一个邻接结点 。
我们可以看到,这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问。
显然,深度优先搜索是一个递归的过程
2. 算法步骤
访问初始结点v,并标记结点v为已访问。
查找结点v的第一个邻接结点w。
若w存在,则继续执行4,如果w不存在,则回到第1步,将从v的下一个结点继续。
若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)。
查找结点v的w邻接结点的下一个邻接结点,转到步骤3。
3.代码实现 添加代码 1 private bool ean[] isVisited;
1 2 3 4 5 6 7 8 9 10 11 12 13 public int getFirstNeighbor (int index) { for (int j = 0 ; j < vertexList.size (); j++) { if (edges[index][j] > 0 ) { return j; } } return -1 ; }
1 2 3 4 5 6 7 8 9 public int getNextNeighbor (int v1, int v2) { for (int j = v2 + 1 ; j < vertexList.size (); j++) { if (edges[v2][j] > 0 ) { return j; } } return -1 ; }
1 2 3 4 5 6 7 8 9 10 11 12 private void dfs(boolean[] isVisited, int i) { System . out.print(getValueByIndex(i ) + "->" ); isVisited[i ] = true ; int w = getFirstNeighbor(i ) ; while (w != -1 ) { if (!isVisited[w ] ) { dfs(isVisited, w); } w = getNextNeighbor(i , w ) ; } }
1 2 3 4 5 6 7 8 public void dfs ( ) { for (int i = 0 ; i < getNumOfVertex(); i++) { if (!isVisited[i]) { dfs(isVisited, i); } } }
1 2 System.out.println("DFS:" ); graph.dfs();// A->B->C->D->E
运行结果 1 2 3 4 5 6 7 [0, 1, 1, 0, 0] [1, 0, 1, 1, 1] [1, 1, 0, 0, 0] [0, 1, 0, 0, 0] [0, 1, 0, 0, 0] DFS: A->B->C->D->E->
广度优先遍历|BFS 1. 基本思想 图的广度优先搜索
类似于一个分层搜索的过程,广度优先遍历需要使用一个队列保持访问过的结点的顺序,以便按照这个顺序来访问这些结点的邻接结点。
2. 算法步骤
访问初始结点v并标记结点v为已访问。
结点v入队列
当队列非空时,继续执行,否则算法结束。
出队列,取得队头结点u。
查找结点u的第一个邻接结点w。
若结点u的邻接结点w不存在,则转到步骤3;否则循环执行以下三个步骤:
若结点w尚未被访问,则访问结点w并标记为已访问。
结点w入队列
查找结点u的继w邻接结点后的下一个邻接结点w,转到步骤6。
3. 代码实现 添加代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private void bfs(boolean[] isVisited, int i) { int u; int w; LinkedList queue = new LinkedList() ; System . out.print(getValueByIndex(i ) + "->" ); isVisited[i ] = true ; queue.addLast(i ) ; while (!queue.isEmpty() ) { u = (Integer) queue.removeFirst() ; w = getFirstNeighbor(u ) ; while (w != -1 ) { if (!isVisited[w ] ) { System . out.print(getValueByIndex(w ) + "->" ); isVisited[w ] = true ; queue.addLast(w ) ; } w = getNextNeighbor(u , w ) ; } } }
1 2 3 4 5 6 7 public void bfs ( ) { for (int i = 0 ; i < getNumOfVertex(); i++) { if (!isVisited[i]) { bfs(isVisited, i); } } }
运行结果 1 2 3 4 5 6 7 [0, 1, 1, 0, 0] [1, 0, 1, 1, 1] [1, 1, 0, 0, 0] [0, 1, 0, 0, 0] [0, 1, 0, 0, 0] BFS: A->B->C->D->E->
参考