图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
图的基本概念
1、顶点(vertex):图中的数据元素;
2、边(edge):图中连接这些顶点的线;
3、路径(Path):在无向图中,若从顶点Vi出发有一组边可到达顶点Vj,则称顶点Vi到顶点Vj的顶点序列为从顶点Vi到顶点Vj的路径;
4、无向图:如果图中任意两个顶点之间的边都是无向边,则称该图为无向图;
5、有向图:如果图中任意两个顶点之间的边都是有向边,则称该图为有向图;
6、有权图:这里的权可以理解成一个数值,就是说节点与节点之间这个边有一个数值与它对应,这个边的值就是代表着两个节点之间的关系,这种图被称为有权图;
7、顶点的度:连接顶点的边的数量称为该顶点的度。对于有向图一个顶点的度有入度和出度之分。
- 入度是以该顶点为端点的入边数量, 记为ID(V)。
- 出度是以该顶点为端点的出边数量, 记为OD(V)。
图的存储结构
1、邻接矩阵(数组):用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
2、邻接表(链表):由表头节点和表节点两部分组成,图中每个顶点均对应一个存储在数组中的表头节点。如果这个表头节点所对应的顶点存在邻接节点,则把邻接节点依次存放于表头节点所指向的单向链表中。
图的遍历
从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次,这个过程称为图的遍历。遍历过程中得到的顶点序列称为图遍历序列。
1、深度优先搜索遍历(DFS)
深度优先搜索思想:
(1)深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点,可以这样理解:每次访问完当前节点后首先访问当前结点的第一个邻接结点。
(2)这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的邻接结点进行横向访问。
(3)深度优先遍历是一个递归过程。
2、广度优先搜索遍历(BFS)
广度优先搜索思想:从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。
/*** 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。*/public interface Graph<V, E> {/*** 新增顶点*/void addVertex(V v);/*** 新增边* @param v1 顶点1* @param v2 顶点2* @param e 权值*/void addEdge(V v1, V v2, E e);/*** 获取边* @param v1 顶点1* @param v2 顶点2*/E getEdge(V v1, V v2);/*** 获取顶点数量*/int size();/*** 获取边数量*/int edgeSize();/*** 获取顶点的索引*/int indexOfVertex(V v);}
/*** 《图》* 邻接矩阵(数组):用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。** 顶点数组: A B C D E* A 0 1 2 3 4* / \* B C ---> 边数组: A B C D E* / \ A 0 1 1 0 0* D -- E B 1 0 0 1 1* C 1 0 0 0 0* D 0 1 0 0 1* E 0 1 0 1 0*/public class ArrayGraph<V, E> implements Graph<V, E> {/*** 存放顶点的一维数组*/private Object[] vertexArray;/*** 存放边的二维数组*/private Object[][] edgeArray;/*** 边的实际数量*/private int edgeSize;/*** 顶点的实际数量*/private int vertexSize;/*** 顶点的最大数量*/private int vertexMaxSize;public ArrayGraph(int vertexMaxSize) {this.vertexSize = 0;this.vertexMaxSize = vertexMaxSize;this.vertexArray = new Object[vertexMaxSize];this.edgeArray = new Object[vertexMaxSize][vertexMaxSize];}/*** 根据索引获取顶点*/public Object getVertexOfIndex(int index) {return vertexArray[index];}/*** 获取顶点的索引*/@Overridepublic int indexOfVertex(V v) {for (int i = 0; i < vertexSize; i++) {if (vertexArray[i] != null && Objects.equals(vertexArray[i], v)) {return i;}}return -1;}/*** 新增顶点*/@Overridepublic void addVertex(V v) {if (vertexSize >= vertexMaxSize) {System.out.println("新增失败,图顶点已满!");return;}vertexArray[vertexSize++] = v;// 同一顶点组成的边权值为0edgeArray[indexOfVertex(v)][indexOfVertex(v)] = 0;}/*** 新增边*/@Overridepublic void addEdge(V v1, V v2, E e) {int index1 = indexOfVertex(v1);if (index1 == -1) {System.out.println("新增失败,顶点不存在" + v1.toString());return;}int index2 = indexOfVertex(v2);if (index2 == -1) {System.out.println("新增失败,顶点不存在" + v2.toString());return;}edgeArray[index1][index2] = e;edgeArray[index2][index1] = e;edgeSize++;}/*** 获取边* @return*/@Overridepublic E getEdge(V v1, V v2) {int index1 = indexOfVertex(v1);if (index1 == -1) {System.out.println("获取失败,顶点不存在" + v1.toString());return null;}int index2 = indexOfVertex(v2);if (index2 == -1) {System.out.println("获取失败,顶点不存在" + v2.toString());return null;}return (E) edgeArray[index1][index2];}/*** 获取顶点数量*/@Overridepublic int size() {return this.vertexSize;}/*** 获取边数量*/@Overridepublic int edgeSize() {return this.edgeSize;}/*** 存放边的二维数组*/public Object[][] getEdgeArray() {return this.edgeArray;}/*** 获取指定顶点的第一个邻接顶点* @return*/private int getFirstNeighbor(int index) {for (int i = 0; i < vertexArray.length; i++) {if (edgeArray[index][i] != null) {return i;}}return -1;}/*** 根据指定顶点的前一个邻接顶点获取下一个邻接顶点* @return*/private int getNextNeighbor(int index, int w) {for (int i = w + 1; i < vertexArray.length; i++) {if (edgeArray[index][i] != null) {return i;}}return -1;}/*** 深度优先搜索遍历(DFS)*/public void dfs() {if (vertexSize == 0) {System.out.println("图为空");return;}boolean[] isVisted = new boolean[vertexSize];dfs(0, isVisted);}/*** 深度优先搜索遍历算法步骤* 1)访问初始节点v,并标记节点v以访问* 2)查找节点v的第一个邻接节点w* 3)若w存在,执行4,若w不存在,回到第一步,从v的下一个节点继续* 4)若w未被访问,对w进行深度优先遍历递归* 5)若w被访问,以下一个邻接节点作为初始节点进行递归*/private void dfs(int v, boolean[] isVisted) {System.out.printf("%s\t", vertexArray[v]);// 设置为访问过isVisted[v] = true;// 获取指定顶点的第一个邻接顶点int w = getFirstNeighbor(v);while (w != -1) {// 当前顶点没有访问过if (!isVisted[w]) {dfs(w, isVisted);}// 访问下一个邻接顶点w = getNextNeighbor(v, w);}}/*** 广度优先搜索遍历(BFS)*/public void bfs() {if (vertexSize == 0) {System.out.println("图为空");return;}boolean[] isVisted = new boolean[vertexSize];bfs(0, isVisted);}/*** 广度优先搜索遍历算法步骤* 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的继ww邻接节点的下一个邻接节点w,转到步骤6*/private void bfs(int v, boolean[] isVisted) {// 输出初始结点vSystem.out.printf("%s\t", vertexArray[v]);// 设置为访问过isVisted[v] = true;// 将访问的结点v加入队列中List<Integer> list = new ArrayList();list.add(v);// 队列不为空时循环执行while (!list.isEmpty()) {// 获取队列头结点int u = list.remove(0);// 获取第一个邻接顶点int w = getFirstNeighbor(u);while (w != -1) {if (!isVisted[w]) {// 输出初始结点wSystem.out.printf("%s\t", vertexArray[w]);// 设置为访问过isVisted[w] = true;// 加入队列list.add(w);}// 访问下一个邻接顶点w = getNextNeighbor(u, w);}}}/*** 打印数组*/public void print() {for (int i = 0; i < vertexSize; i++) {for (int j=0; j < vertexSize; j++) {System.out.printf("%s\t", edgeArray[i][j] == null ? "-" : edgeArray[i][j]);}System.out.println();}}public static void main(String[] args) {ArrayGraph<String, Integer> arrayGraph = new ArrayGraph<>(5);// 添加顶点arrayGraph.addVertex("A");arrayGraph.addVertex("B");arrayGraph.addVertex("C");arrayGraph.addVertex("D");arrayGraph.addVertex("E");// 添加边arrayGraph.addEdge("A", "B", 1);arrayGraph.addEdge("A", "C", 1);arrayGraph.addEdge("B", "D", 1);arrayGraph.addEdge("B", "E", 1);arrayGraph.addEdge("D", "E", 1);System.out.println("打印邻接矩阵:");arrayGraph.print();System.out.println("获取顶点数量:" + arrayGraph.size());System.out.println("获取边数量:" + arrayGraph.edgeSize());System.out.println("获取边:" + arrayGraph.getEdge("A", "B"));System.out.println("深度优先搜索遍历(DFS):");arrayGraph.dfs();System.out.println();System.out.println("广度优先搜索遍历(BFS):");arrayGraph.bfs();}}
