961全部内容链接
广度优先搜索(Breadth-First-Search,BFS)类似于树中的层序遍历。基本思想为:
1. 首 先 访 问 起 始 顶 点 v , 然 后 依 次 访 问 顶 点 v 的 邻 接 顶 点 w 1 , w 2 , ⋯ , w i 2. 访 问 w 1 顶 点 的 邻 接 顶 点 w 11 , w 12 , ⋯ 3. 访 问 w 2 顶 点 的 邻 接 顶 点 4. 依 次 循 环 下 去 , 直 到 该 连 通 分 量 的 顶 点 全 部 访 问 完 毕 。 5. 若 还 存 在 其 他 未 访 问 的 顶 点 ( 其 他 未 访 问 的 连 通 分 量 ) , 则 重 复 1 , 2 , 3 , 4 , 直 到 所 有 节 点 都 被 访 问 \begin{aligned} & 1. 首先访问起始顶点v,然后依次访问顶点v的邻接顶点w_1,w_2,\cdots,w_i \\ & 2. 访问w_1顶点的邻接顶点w_{11},w_{12},\cdots \\ & 3. 访问w_2顶点的邻接顶点 \\ & 4. 依次循环下去,直到该连通分量的顶点全部访问完毕。\\ & 5. 若还存在其他未访问的顶点(其他未访问的连通分量),则重复1,2,3,4,直到所有节点都被访问 \end{aligned} 1.首先访问起始顶点v,然后依次访问顶点v的邻接顶点w1,w2,⋯,wi2.访问w1顶点的邻接顶点w11,w12,⋯3.访问w2顶点的邻接顶点4.依次循环下去,直到该连通分量的顶点全部访问完毕。5.若还存在其他未访问的顶点(其他未访问的连通分量),则重复1,2,3,4,直到所有节点都被访问
该算法类似树中的层序遍历。若该图为一棵树,那么该图的广度优先遍历的结果就是树的层序遍历结果。
举例:
该图的广度优先遍历结果为:1,2,5,6,3,7,4,8
该图(树)的广度优先遍历结果为:1,2,3,4,5,6,7,8
与树的层序遍历类似,需要借助一个辅助队列。具体操作为:
Java代码如下:
public static void bsfSearch(Graph graph) {
HashSet visited = new HashSet(); // 记录已经访问的顶点
for (int i = 0; i < graph.getVertexNumber(); i++) {
// 遍历所有节点,若节点没有被访问过,那么就对这个节点进行BSF操作。
if (!visited.contains(graph.getVertexByIndex(i)))
bsf(graph, graph.getVertexByIndex(i), visited);
}
}
private static void bsf(Graph graph, Object vertex, HashSet visited) {
Queue queue = new ArrayDeque(); // 初始化队列
queue.add(vertex);
while (!queue.isEmpty()) {
// 当队列为空时,说明该连通分量的BSF结束了
Object head = queue.remove();
if (visited.contains(head)) continue; // 如果该节点访问过,则不需要再访问。
System.out.println(head); // 访问节点
visited.add(head); // 将访问过的节点增添到已访问列表中
Object[] neighborVertex = graph.neighbors(head); // 获取该节点的邻接节点。
for (int i = 0; i < neighborVertex.length; i++) {
// 将该节点的所有邻接入队
queue.add(neighborVertex[i]);
}
}
}
空间复杂度:因为要申请一个队列,还需要申请一个空间保存被访问过的节点。所以时间复杂度是O(|V|),其中|V|为图中节点的数量
时间复杂度:对于采用不同的方式实现,时间复杂度不一致。不一致的来源主要是为“访问节点的邻接节点”这一步。该步骤一共需要做 |V| 次。 若采用邻接矩阵进行实现,则每次访问邻接节点时,要扫描那一行为1的元素,时间复杂度为O(|V|^2)。若采用邻接表方式实现,则访问所有的邻接节点,一共访问了|E|次(|E|是总的边数)。所以时间复杂度为O(|V|+|E|)
单源最短路径:一个节点(单源)u到其他节点的最短路径,就是他的单源最短路径。
思想:因为广度优先遍历(BSF)是层序遍历的思想,假设从u出发,那么v节点相对于u节点的最浅层次,就是它的最短路径。
如图,假设求 (6,8) 的最短路径,有很多路径,比如 “6,7,8”,“6,3,7,8”,“6,3,4,8”。但是把它们想成一棵树,然后进行层序遍历,即BFS。这样的话,对于“6,7,8”这条路径,8在6的第三层。对于路径“6,3,7,8”,“6,3,4,8”,8相当于6的第四层。所以按照BFS算法,一定会先通过“678”这条路径访问8。
Java代码如下:
/**
* 返回vertex顶点到其他所有顶点的距离
* @param graph 图
* @param vertex 要求的顶点
* @return Map<顶点, vertex到顶点的距离>
*/
public static Map<Object, Integer> bsfMinDistance(Graph graph, Object vertex) {
HashSet visited = new HashSet(); // 记录访问过的节点
Queue queue = new ArrayDeque(); // bsf辅助队列
Map<Object, Integer> result = new HashMap<>();
for (int i = 0; i < graph.getVertexNumber(); i++) {
result.put(graph.getVertexByIndex(i), -1); // 初始化结果,-1代表不可达
}
result.put(vertex, 0); // 将要求的顶点的距离初始化为0,因为自己到自己的距离是0
// 开始进行bsf
queue.add(vertex);
visited.add(vertex);
while(!queue.isEmpty()) {
Object head = queue.remove(); // 队头出队
Object[] neighbors = graph.neighbors(head); // 求出队头节点的邻接节点
for (int i = 0; i < neighbors.length; i++) {
if (!visited.contains(neighbors[i])) {
visited.add(neighbors[i]);
result.put(neighbors[i], result.get(head) + 1); //将其邻接节点的距离设置为vertex节点到队头节点的距离再加1。
queue.add(neighbors[i]); // 队头节点的邻接节点入队
}
}
}
return result;
}
因为广度优先遍历是从某一个顶点出发,然后进行层序遍历,所以广度优先算法可以对一个连通分量生成一棵树。如果是邻接矩阵存储,只要节点的顺序定下来了,那么访问邻接节点的顺序就是固定的,那么生成的树也是固定的。若使用邻接表进行存储,即是节点顺序固定下来,邻接表也是不唯一的,所以生成的树也是不唯一的。这个应该不考代码,了解即可。
深度优先搜索类似树的先序遍历,基本思想为:
首先访问节点v,然后访问v的第一个邻接节点w1,然后访问w1的邻接节点w2,一直访问下去,直到该条路走到底。然后接着访问v的第二个邻接节点w2,然后再次进行,直到所有的节点都访问完毕
Java代码如下:
public static void dsfSearch(Graph graph) {
HashSet visited = new HashSet(); // 存储已经访问过的节点
for (int i = 0; i < graph.getVertexNumber(); i++) {
// 对所有未访问过的节点进行DSF
dsf(graph, graph.getVertexByIndex(i), visited);
}
}
private static void dsf(Graph graph, Object vertex, HashSet visited) {
if (visited.contains(vertex)) return; // 若该节点已经访问过了,则退出。
System.out.println(vertex); // 访问该节点
visited.add(vertex); // 将该节点标记为已访问
Object[] neighborVertexes = graph.neighbors(vertex); // 查找该节点的邻接顶点
for (int i = 0; i < neighborVertexes.length; i++) {
// 对该节点的所有邻接节点进行DFS
dsf(graph, neighborVertexes[i], visited);
}
}
空间复杂度:要借助一个集合来存储已经访问过的顶点。因为是递归,还会有一个递归工作栈。最坏的情况下,递归工作栈的深度就是顶点的数量。所以空间复杂度为 O(|V|)
时间复杂度:与BFS算法一样,不同存储方式的时间消耗差异主要在查找邻接节点上。所以,当使用邻接矩阵进行存储时,时间复杂度为 O(|V|^2),但使用邻接表进行查找时,时间复杂度为O(|V|+|E|)
与广度优先搜索一样,对图进行深度优先搜索也会产生一棵树。若该图有多个连通分量,则会产生多个树,即森林。同样,邻接矩阵存储时,树是唯一的。邻接表存储时,树不唯一。