图(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

图的存储结构

1、邻接矩阵(数组):用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
图 - 图2
2、邻接表(链表):表头节点表节点两部分组成,图中每个顶点均对应一个存储在数组中的表头节点。如果这个表头节点所对应的顶点存在邻接节点,则把邻接节点依次存放于表头节点所指向的单向链表中。
图 - 图3

图的遍历

从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次,这个过程称为图的遍历。遍历过程中得到的顶点序列称为图遍历序列

1、深度优先搜索遍历(DFS)
深度优先搜索思想:
(1)深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点,可以这样理解:每次访问完当前节点后首先访问当前结点的第一个邻接结点。
(2)这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的邻接结点进行横向访问。
(3)深度优先遍历是一个递归过程。

图 - 图4

2、广度优先搜索遍历(BFS)
广度优先搜索思想:从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

图 - 图5

  1. /**
  2. * 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
  3. */
  4. public interface Graph<V, E> {
  5. /**
  6. * 新增顶点
  7. */
  8. void addVertex(V v);
  9. /**
  10. * 新增边
  11. * @param v1 顶点1
  12. * @param v2 顶点2
  13. * @param e 权值
  14. */
  15. void addEdge(V v1, V v2, E e);
  16. /**
  17. * 获取边
  18. * @param v1 顶点1
  19. * @param v2 顶点2
  20. */
  21. E getEdge(V v1, V v2);
  22. /**
  23. * 获取顶点数量
  24. */
  25. int size();
  26. /**
  27. * 获取边数量
  28. */
  29. int edgeSize();
  30. /**
  31. * 获取顶点的索引
  32. */
  33. int indexOfVertex(V v);
  34. }
  1. /**
  2. * 《图》
  3. * 邻接矩阵(数组):用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
  4. *
  5. * 顶点数组: A B C D E
  6. * A 0 1 2 3 4
  7. * / \
  8. * B C ---> 边数组: A B C D E
  9. * / \ A 0 1 1 0 0
  10. * D -- E B 1 0 0 1 1
  11. * C 1 0 0 0 0
  12. * D 0 1 0 0 1
  13. * E 0 1 0 1 0
  14. */
  15. public class ArrayGraph<V, E> implements Graph<V, E> {
  16. /**
  17. * 存放顶点的一维数组
  18. */
  19. private Object[] vertexArray;
  20. /**
  21. * 存放边的二维数组
  22. */
  23. private Object[][] edgeArray;
  24. /**
  25. * 边的实际数量
  26. */
  27. private int edgeSize;
  28. /**
  29. * 顶点的实际数量
  30. */
  31. private int vertexSize;
  32. /**
  33. * 顶点的最大数量
  34. */
  35. private int vertexMaxSize;
  36. public ArrayGraph(int vertexMaxSize) {
  37. this.vertexSize = 0;
  38. this.vertexMaxSize = vertexMaxSize;
  39. this.vertexArray = new Object[vertexMaxSize];
  40. this.edgeArray = new Object[vertexMaxSize][vertexMaxSize];
  41. }
  42. /**
  43. * 根据索引获取顶点
  44. */
  45. public Object getVertexOfIndex(int index) {
  46. return vertexArray[index];
  47. }
  48. /**
  49. * 获取顶点的索引
  50. */
  51. @Override
  52. public int indexOfVertex(V v) {
  53. for (int i = 0; i < vertexSize; i++) {
  54. if (vertexArray[i] != null && Objects.equals(vertexArray[i], v)) {
  55. return i;
  56. }
  57. }
  58. return -1;
  59. }
  60. /**
  61. * 新增顶点
  62. */
  63. @Override
  64. public void addVertex(V v) {
  65. if (vertexSize >= vertexMaxSize) {
  66. System.out.println("新增失败,图顶点已满!");
  67. return;
  68. }
  69. vertexArray[vertexSize++] = v;
  70. // 同一顶点组成的边权值为0
  71. edgeArray[indexOfVertex(v)][indexOfVertex(v)] = 0;
  72. }
  73. /**
  74. * 新增边
  75. */
  76. @Override
  77. public void addEdge(V v1, V v2, E e) {
  78. int index1 = indexOfVertex(v1);
  79. if (index1 == -1) {
  80. System.out.println("新增失败,顶点不存在" + v1.toString());
  81. return;
  82. }
  83. int index2 = indexOfVertex(v2);
  84. if (index2 == -1) {
  85. System.out.println("新增失败,顶点不存在" + v2.toString());
  86. return;
  87. }
  88. edgeArray[index1][index2] = e;
  89. edgeArray[index2][index1] = e;
  90. edgeSize++;
  91. }
  92. /**
  93. * 获取边
  94. * @return
  95. */
  96. @Override
  97. public E getEdge(V v1, V v2) {
  98. int index1 = indexOfVertex(v1);
  99. if (index1 == -1) {
  100. System.out.println("获取失败,顶点不存在" + v1.toString());
  101. return null;
  102. }
  103. int index2 = indexOfVertex(v2);
  104. if (index2 == -1) {
  105. System.out.println("获取失败,顶点不存在" + v2.toString());
  106. return null;
  107. }
  108. return (E) edgeArray[index1][index2];
  109. }
  110. /**
  111. * 获取顶点数量
  112. */
  113. @Override
  114. public int size() {
  115. return this.vertexSize;
  116. }
  117. /**
  118. * 获取边数量
  119. */
  120. @Override
  121. public int edgeSize() {
  122. return this.edgeSize;
  123. }
  124. /**
  125. * 存放边的二维数组
  126. */
  127. public Object[][] getEdgeArray() {
  128. return this.edgeArray;
  129. }
  130. /**
  131. * 获取指定顶点的第一个邻接顶点
  132. * @return
  133. */
  134. private int getFirstNeighbor(int index) {
  135. for (int i = 0; i < vertexArray.length; i++) {
  136. if (edgeArray[index][i] != null) {
  137. return i;
  138. }
  139. }
  140. return -1;
  141. }
  142. /**
  143. * 根据指定顶点的前一个邻接顶点获取下一个邻接顶点
  144. * @return
  145. */
  146. private int getNextNeighbor(int index, int w) {
  147. for (int i = w + 1; i < vertexArray.length; i++) {
  148. if (edgeArray[index][i] != null) {
  149. return i;
  150. }
  151. }
  152. return -1;
  153. }
  154. /**
  155. * 深度优先搜索遍历(DFS)
  156. */
  157. public void dfs() {
  158. if (vertexSize == 0) {
  159. System.out.println("图为空");
  160. return;
  161. }
  162. boolean[] isVisted = new boolean[vertexSize];
  163. dfs(0, isVisted);
  164. }
  165. /**
  166. * 深度优先搜索遍历算法步骤
  167. * 1)访问初始节点v,并标记节点v以访问
  168. * 2)查找节点v的第一个邻接节点w
  169. * 3)若w存在,执行4,若w不存在,回到第一步,从v的下一个节点继续
  170. * 4)若w未被访问,对w进行深度优先遍历递归
  171. * 5)若w被访问,以下一个邻接节点作为初始节点进行递归
  172. */
  173. private void dfs(int v, boolean[] isVisted) {
  174. System.out.printf("%s\t", vertexArray[v]);
  175. // 设置为访问过
  176. isVisted[v] = true;
  177. // 获取指定顶点的第一个邻接顶点
  178. int w = getFirstNeighbor(v);
  179. while (w != -1) {
  180. // 当前顶点没有访问过
  181. if (!isVisted[w]) {
  182. dfs(w, isVisted);
  183. }
  184. // 访问下一个邻接顶点
  185. w = getNextNeighbor(v, w);
  186. }
  187. }
  188. /**
  189. * 广度优先搜索遍历(BFS)
  190. */
  191. public void bfs() {
  192. if (vertexSize == 0) {
  193. System.out.println("图为空");
  194. return;
  195. }
  196. boolean[] isVisted = new boolean[vertexSize];
  197. bfs(0, isVisted);
  198. }
  199. /**
  200. * 广度优先搜索遍历算法步骤
  201. * 1)访问初始节点v并标记v为已访问
  202. * 2)节点v入队列
  203. * 3)当队列非空时,继续执行,否则算法结束
  204. * 4)出队列,取得队头节点u
  205. * 5)查找节点u的第一个邻接节点w
  206. * 6)若节点u的邻接节点w不存在,进行步骤3,否则循环执行以下三个步骤
  207. * 6.1若w未被访问,则访问节点w并记为已访问
  208. * 6.2节点w入队列
  209. * 6.3查找节点u的继ww邻接节点的下一个邻接节点w,转到步骤6
  210. */
  211. private void bfs(int v, boolean[] isVisted) {
  212. // 输出初始结点v
  213. System.out.printf("%s\t", vertexArray[v]);
  214. // 设置为访问过
  215. isVisted[v] = true;
  216. // 将访问的结点v加入队列中
  217. List<Integer> list = new ArrayList();
  218. list.add(v);
  219. // 队列不为空时循环执行
  220. while (!list.isEmpty()) {
  221. // 获取队列头结点
  222. int u = list.remove(0);
  223. // 获取第一个邻接顶点
  224. int w = getFirstNeighbor(u);
  225. while (w != -1) {
  226. if (!isVisted[w]) {
  227. // 输出初始结点w
  228. System.out.printf("%s\t", vertexArray[w]);
  229. // 设置为访问过
  230. isVisted[w] = true;
  231. // 加入队列
  232. list.add(w);
  233. }
  234. // 访问下一个邻接顶点
  235. w = getNextNeighbor(u, w);
  236. }
  237. }
  238. }
  239. /**
  240. * 打印数组
  241. */
  242. public void print() {
  243. for (int i = 0; i < vertexSize; i++) {
  244. for (int j=0; j < vertexSize; j++) {
  245. System.out.printf("%s\t", edgeArray[i][j] == null ? "-" : edgeArray[i][j]);
  246. }
  247. System.out.println();
  248. }
  249. }
  250. public static void main(String[] args) {
  251. ArrayGraph<String, Integer> arrayGraph = new ArrayGraph<>(5);
  252. // 添加顶点
  253. arrayGraph.addVertex("A");
  254. arrayGraph.addVertex("B");
  255. arrayGraph.addVertex("C");
  256. arrayGraph.addVertex("D");
  257. arrayGraph.addVertex("E");
  258. // 添加边
  259. arrayGraph.addEdge("A", "B", 1);
  260. arrayGraph.addEdge("A", "C", 1);
  261. arrayGraph.addEdge("B", "D", 1);
  262. arrayGraph.addEdge("B", "E", 1);
  263. arrayGraph.addEdge("D", "E", 1);
  264. System.out.println("打印邻接矩阵:");
  265. arrayGraph.print();
  266. System.out.println("获取顶点数量:" + arrayGraph.size());
  267. System.out.println("获取边数量:" + arrayGraph.edgeSize());
  268. System.out.println("获取边:" + arrayGraph.getEdge("A", "B"));
  269. System.out.println("深度优先搜索遍历(DFS):");
  270. arrayGraph.dfs();
  271. System.out.println();
  272. System.out.println("广度优先搜索遍历(BFS):");
  273. arrayGraph.bfs();
  274. }
  275. }