一、广度优先遍历 (BFS)
广度优先遍历顾名思义就是优先搜索横向范围内的数据,直到找到目标节点为止,用于求解最短路径问题。
算法思想:从起点开始,将起点的所有子节点添加进一个队列中(已经遍历过的节点不可重复添加),然后依次类推,将子节点的子节点也加入队列中,当队列为空时或者找到目标节点时算法结束。
如上图,求A到G的最短距离(默认每个边的权值为1),并输出
广度优先遍历的求解步骤:
- 将起点A放入到队列S中,S={A},用Depth表示距离,默认值为0
- 从队列S中取出第一个元素,因为队列有先进先出(FIFO)原则,所以我们可以得到节点A,然后将节点A的所有子节点添加到队列S中,此时S={B,C},此时队列S中的节点都为同级节点,所以Depth = Depth+1,即Depth = 1
- 重复第2步操作,此时S={C,D,E},此时因为队列S中的节点不都是同级节点,所以Depth的值保持不变,
- 重复第2步操作,此时S={D,E,F},此时队列S中同时存在B节点和D节点的子节点,但是因为节点B和节点D是同级节点,所以他们的子节点也是同级节点,即此时队列S中的节点都为同级节点,所以Depth = Depth+1,即Depth = 2
- 此时我们从队列中取出的元素为D,D节点的子元素有节点B和E,因为节点B已经遍历过,并且已经出了队列,而节点E当前正在队列中,所以不需要重复添加,即S={E,F},虽然此时队列S中的节点为同级节点,但是在步骤4中我们已经对节点E和节点F的同级节点进行过一次累加,所以此时Depth不需要改变
- 重复第2步操作,此时出队列的为节点E,节点E的子元素有节点B,D,F,G,这四个子节点中除了节点G还没有遍历过,并且不再队列S中,所以只需要将节点G加入队列S即可,S={F,G},此时队列S中的节点不都是同级节点,所以Depth不变
- 再次从队列中取出一个元素,即元素F,节点F的所有子节点都已经遍历过,所以不需要改变队列S,即S={G},此时队列S中的元素为同级元素,所以Depth = Depth+1,即Depth = 3
- 再次从队列中取出一个元素,即元素G,节点G的子节点只有节点E,但是E已经别遍历过了,所以不能添加进队列S,即S={},当S为空时,算法结束。
注:Depth应该在什么时候累加,什么时候不累加呢?可以采用两种方法判断:
方法一:标记法就是在队列S中添加一个特殊的标记位,当从队列中取出的元素为标记位时Depth加累加一次。
以上图为例:节点A的子元素有节点B和C,所以当节点B和节点C进入队列后,应该在其后添加一个标记位(以@为标记),S={B,C,@},当节点B出队列后,S={C,@,D,E};
节点C出队列后,S={@,D,E,F};
当再次从队列中取出元素时,我们发现该元素时标记位@,所以此时Depth = Depth + 1,并且更新S={D,E,F,@},所以当取出的元素为标记位时,应该做两步操作,第一步是给Depth加1,第二步给队列中添加新的标记位。
方法二:计数法使用两个变量Count1和Count2,分别统计队列S中当前节点的同级的子节点个数(Count1)和当前节点的同级的子节点的同级的子节点个数(Count2).当Count1 = 0,Count2 != 0时,Depth = Depth+1.
已上图为例:节点A的同级子节点有节点B和节点C,所以Count1 = 2,Count2 = 0,
节点B出队列后,Count1 = 1,Count2 =2,因为节点B有两个子节点D和E,所以Count2 =2,因为节点B出队列了,所以Count1 = 1;
节点C出队列后,Count1 = 0,Count2 =3,此时**因为Count1=0,表示存在一级的元素已经全部出了队列,所以Depth = Depth+1.并且Count1 = Count2,Count2 = 0;**以此类推就可计算出A到G的最短距离。
算法实现:
首先将上图转换为邻接矩阵(1:相连;0:不相连):
JAVA代码实现
static int[][] source;
public static void main(String[] args) {
source = new int[][] {
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 1, 0 },
{ 0, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 1, 0, 1, 1 },
{ 0, 0, 0, 1, 0, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0 } };
int r = bfs(1, 7);
System.out.println(r);
}
public static int bfs(int start, int end) {
int depth = 0;
//此处不能对队列中的元素排序
LinkedBlockingQueue<Integer> que = new LinkedBlockingQueue<Integer>();
que.add(start);
source[start][0] = 1;
while (!que.isEmpty()) {
int n = que.poll();
if (n == -1) {//-1为标记位
depth++;
que.add(-1);
continue;
}
if (n == end) {
return ++depth;
}
for (int i = 1; i < source[n].length; i++) {
if (source[n][i] != 0 && source[i][0] != 1) {
que.add(i);
source[i][0] = 1;//表示节点i,已经遍历过了
}
}
if (n == start) {
que.add(-1);
}
}
return depth;
}
//采用计数的方法
public static int bfs2(int start, int end) {
int depth = 0, c1 = 0, c2 = 0;
// 此处不能对队列中的元素排序
LinkedBlockingQueue<Integer> que = new LinkedBlockingQueue<Integer>();
que.add(start);
source[start][0] = 1;
while (!que.isEmpty()) {
int n = que.poll();
if (c1 == 0 && c2 != 0) {
c1 = c2;
c2 = 0;
depth++;
}
if (c1 != 0) {
c1--;
}
if (n == end) {
return depth;
}
for (int i = 1; i < source[n].length; i++) {
if (source[n][i] != 0 && source[i][0] != 1) {
que.add(i);
c2++;
source[i][0] = 1;// 表示节点i,已经遍历过了
}
}
}
return depth;
}
二、深度优先遍历 (DFS)
深度优先遍历顾名思义就是优先搜索纵向范围内的数据,直到找到目标节点为止。采用递归调用的思想,优先在纵向范围内查找。也就是说当找到某一个节点的子节点时,继续深入查找该子节点的子节点,而不是查找该子节点的兄弟节点。
如上图,从节点A开始,将所有节点按照深度优先遍历的方式输出
算法步骤:
- 首先查找节点A的子节点,A的子节点有节点B和节点C;
- 选择节点A的子节点B,继续查询节点B的子节点,节点B的子节点有节点D和节点E
- 选择节点B的子节点D,继续查找节点D的子节点,节点D的子节点有节点B和节点E;
- 选择节点D的子节点,因为节点D的子节点B在第3步中已经访问(已访问过的节点不能重复访问),所以我们选择节点E;
- 节点E的子节点有节点F和节点G,选择子节点F,继续查找子节点F的子节点,节点F有子节点C;
- 选择节点F的子节点C,继续查询节点C的子节点,节点C的子节点只有节点A,但是节点A已经访问过了,所以此时我们需要向上回溯;
- 回溯到节点F,节点F除了子节点C外,在没有其他子节点,所以继续向上回溯;
- 回溯到节点E,节点E除了子节点F还有子节点G,并且节点G没有被访问过,所以此时我们选择节点G,继续查询节点G的子节点;
- 因为节点G已经是叶子节点,所以我们继续向上回溯;
- 回溯到节点E,但是节点E的两个子节点F和G。都已经被访问过了,所以继续向上回溯;
- 回溯到节点D,发现节点D的两个子节点都已经被访问过,继续向上回溯;
- 回溯到节点B,节点B的两个子节点也是都被访问过了,继续向上回溯;
- 回溯到节点A,节点A的子节点都已经被访问过(节点C作为节点F的子节点被访问过了,所以当回溯到节点A时,节点C的实际状态是被访问过的状态),至此所有节点都已经被访问,算法结束
算法实现,首先将上图转换为邻接矩阵:
JAVA代码实现:
//main方法和BFS的一致
public static void dfs(int start, int end) {
source[start][0] = 2;
System.out.println(start);
for (int i = 1; i < source[start].length; i++) {
if (source[start][i] != 0 && source[i][0] != 2) {
source[i][0] = 2;
dfs(i, end);
}
}
}
运行结果: