图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。 结点也可以称为顶点。如图:
在考虑代码实现中,我们可以通过一个列表来存储所有的结点,数组来存储所有边。
package com.hong.graph; import java.util.ArrayList; import java.util.Arrays; public class Graph { private ArrayList<String> vertexList; //存储顶点集合 private int[][] edges; //存储图对应的邻结矩阵 private int numOfEdges; //表示边的数目 //定义给数组boolean[], 记录某个结点是否被访问 private boolean[] isVisited; //构造器 public Graph(int n) { //初始化矩阵和vertexList 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 void insertVertex(String vertex) { vertexList.add(vertex); } //添加边 /** * * @param v1 表示点的下标即使第几个顶点 "A"-"B" "A"->0 "B"->1 * @param v2 第二个顶点对应的下标 * @param weight 表示 */ public void insertEdge(int v1, int v2, int weight) { edges[v1][v2] = weight; edges[v2][v1] = weight; numOfEdges++; } }我们以上面的图来举例说明图的深度优先遍历是怎么样的一个过程。
以A为起始节点,找到它的第一个邻接节点B;接着以B为起始节点(这里其实是对B的一个递归过程),找到它的第一个邻接节点C;然后,以B为起始节点(对C的一个递归过程),发现它的邻接节点A和B都已经被访问过了,退出C的递归;这时候,需要对B进行回溯,找到B的第二个邻接节点D;再接着,以D为起始节点(对D的一个递归过程),发现它的邻接节点B已经被访问过了,退出D的递归;继续对B进行回溯,找到B的第三个邻接节点E;再接着,以E为起始节点(对E的一个递归过程),发现它的邻接节点B已经被访问过了,退出E的递归;继续对B进行回溯,发现没有其他的邻接节点了,退出B的递归;此时,就需要对A进行回溯了,找到它的第二个邻接点D,发现它的邻接节点B已经被访问过了,退出D的递归;最后,又对A进行回溯, 没有其他的邻接节点了,完成对A的整个深度优先遍历;下面,就是对其他剩下的所有节点,都执行以上10步的操作(但其实很多节点都已经被访问过了,所以很多节点都是跳过的)。 /** * 深度优先遍历主方法 */ public void deepTraversing(){ isVisited = new boolean[vertexList.size()]; for (int i=0; i<vertexList.size(); i++){ if (!isVisited[i]){ deepTraversing(i); } } } /** * 以vertex为起点,进行深度遍历递归 * @param vertex */ private void deepTraversing(int vertex){ System.out.print(vertexList.get(vertex) + "->"); isVisited[vertex] = true; // 获取vertex的第一个邻接节点 int next = getNextEdges(vertex, 0); while (next != -1){ if (!isVisited[next]){ deepTraversing(next); } else{ // 如果该邻接节点已经被访问过,则获取下一个邻接节点 next = getNextEdges(vertex, next+1); } } } /** * 获取vertex从初始位置为start的下一个邻接节点 * @param vertex * @param start * @return */ private int getNextEdges(int vertex, int start){ for (int j=start; j<vertexList.size(); j++){ if (edges[vertex][j] == 1){ return j; } } return -1; }广度优先遍历比深度优先遍历简单很多,比较容易理解。仍然以上面的图为例子:
从第一个节点A开始,依次找到它的所有邻接节点;然后,轮到第二个节点B,依次找到它的所有邻接节点(未被访问的);剩下的节点类似… /** * 广度优先遍历方法 */ private void breadthTraversing(){ isVisited = new boolean[vertexList.size()]; int w; for (int v=0; v<vertexList.size(); v++){ // 打印顶点v的所有邻接节点(未被访问过的) if (!isVisited[v]){ System.out.print(vertexList.get(v) + "->"); isVisited[v] = true; } w = getNextEdges(v, 0); // 获取v的第一个邻接节点 while (w != -1){ if (!isVisited[w]){ System.out.print(vertexList.get(w) + "->"); isVisited[w] = true; } w = getNextEdges(v, w+1); } } }完整代码已上传至GitHub
为什么很多人对图的广度优先遍历,都是通过队列来实现的。就感觉是对前面的节点进行循环操作时,先帮后面的一些节点完成遍历; 但,其实不用队列的话,就是每个节点完成自己的遍历。 有大神看到的帮忙解答一下!!!
欢迎关注同名公众号:“我就算饿死也不做程序员”。 交个朋友,一起交流,一起学习,一起进步。