广度优先搜索,该算法是将已发现结点和未发现结点之间的边界,沿着其广度方向向外扩展,算法需要发现所有距离源结点 s 为 k 的所有结点之后,才会发现距离源结点 s 为 k+1 的其他结点。如果结点都被访问,算法终止。
此过程需要先构建一颗广度优先树。一开始,该树只有根结点 s (源节点)。在扫描已发现结点 s 的邻接表时,每发现一个白色结点 v,就把结点染黑(这是为了记录访问的痕迹),并把它们之间的边(u, v)加入广度优先树。
该算法使用了一个具有 FIFO 特性的队列来管理已知和未知两个集合之间的边界。其中,一个数组记录结点的信息,一个存储结点之间边的关系的数组,还有一个数组来记录结点的访问痕迹。
广度优先搜索算法:
1 /** 2 * 广度优先遍历 3 */ 4 public void BFS() { 5 Queue<Integer> queue = new LinkedList<>(); 6 // 遍历每个顶点 7 for (int i = 0; i < vertexSize; i++) { 8 if (!visited[i]) { 9 queue.add(i); // 当前节点入队 10 visited[i] = true; 11 System.out.print(vertexesArray[i] + " "); 12 13 // 队列不为空时 14 while (!queue.isEmpty()) { 15 // 移除当前队列队首的顶点 16 int row = queue.remove(); 17 18 // 遍历当前队首的顶点能指向的所有节点(距离为1) 19 for (int k = firstAdjVex(row); k >= 0; k = nextAdjVex(row, k)) { 20 if (!visited[k]) { 21 queue.add(k); 22 visited[k] = true; 23 System.out.print(vertexesArray[k] + " "); 24 } 25 } 26 } 27 } 28 } 29 }
寻找当前结点能指向的节点:
1 /** 2 * 能指向的第一个节点 3 * @param row 当前节点 4 * @return 当前节点能指向的第一个节点位置,不存在返回-1 5 */ 6 private int firstAdjVex(int row) { 7 for (int column = 0; column < vertexSize; column++) { 8 if (edgesMatrix[row][column] == 1) 9 return column; 10 } 11 return -1; 12 } 13 14 /** 15 * 接下来指向的下一个节点 16 * @param row 当前节点 17 * @param k 从顶点表的k位置,找当前节点能指向的结点(k位置之后的结点,k及k之前得到找过了一遍) 18 * @return 下一个节点位置,不存在返回-1 19 */ 20 private int nextAdjVex(int row, int k) { 21 for (int j = k + 1; j < vertexSize; j++) { 22 if (edgesMatrix[row][j] == 1) 23 return j; 24 } 25 return -1; 26 }
分析:队列入队出队时间复杂度为O(1),所以队列总的操作时间为O(V);在队列中的结点出队的时候才回去扫描这个结点的邻接表,每个邻接表只扫描一次,总的时间为O(E)。因此总的运行时间为O(V+E)。
深度优先搜索,总是对最近发现的结点 v 的出发边进行探索,直到该结点的所有出发边都被发现为止。一但所有出发边都被发现,搜索则“回溯”到 v 的前驱结点(v 是经过结点才被发现的),来搜索改前去结点的出发边。
1 public void DFS(Object o) { 2 int index = -1; 3 // 遍历所有节点中是否存在目的地 4 for (int i = 0; i < vertexSize; i++) { 5 if (vertexesArray[i].equals(o)) { 6 index = i; 7 break; 8 } 9 } 10 11 if (index == -1) { 12 new NullPointerException("不存该值" + o); 13 } 14 15 // 初始化所有节点的访问痕迹 16 for (int i = 0; i < vertexSize; i++) { 17 visited[i] = false; 18 } 19 20 traverse(index);// 有向图 21 22 // 无向图,依次将每个顶点遍历能抵达的所有边 23 if (graphType) { 24 for (int i = 0; i < vertexSize; i++) { 25 if (!visited[i]) 26 traverse(i); 27 } 28 } 29 } 30 31 /** 32 * 深度优先就是由开始点向最深处遍历,没有了就回溯到上一级顶点 33 * @param i 当前顶点 34 */ 35 private void traverse(int i) { 36 visited[i] = true; 37 System.out.print(vertexesArray[i] + " "); 38 39 for (int j = firstAdjVex(i); j >= 0; j = nextAdjVex(i, j)) { 40 if (!visited[j]) { 41 traverse(j); 42 } 43 } 44 }
分析:DFS()的两个循环所需时间为O(V),traverse()中对每个结点的邻接表进行扫描,循环总次数加起来为O(E),所以时间复杂度为O(V+E)。