算法学习--广度优先遍历和深度优先遍历

本文详细介绍了广度优先遍历(BFS)和深度优先遍历(DFS)两种算法,用于解决图的遍历问题。BFS适用于寻找最短路径,而DFS采用递归思想,优先深入查找。通过实例解释了两种算法的工作原理,包括在实际操作中如何判断Depth何时累加,并提供了JAVA代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、广度优先遍历 (BFS)

广度优先遍历顾名思义就是优先搜索横向范围内的数据,直到找到目标节点为止,用于求解最短路径问题
算法思想:从起点开始,将起点的所有子节点添加进一个队列中(已经遍历过的节点不可重复添加),然后依次类推,将子节点的子节点也加入队列中,当队列为空时或者找到目标节点时算法结束。
在这里插入图片描述
如上图,求A到G的最短距离(默认每个边的权值为1),并输出
广度优先遍历的求解步骤:

  1. 将起点A放入到队列S中,S={A},用Depth表示距离,默认值为0
  2. 从队列S中取出第一个元素,因为队列有先进先出(FIFO)原则,所以我们可以得到节点A,然后将节点A的所有子节点添加到队列S中,此时S={B,C},此时队列S中的节点都为同级节点,所以Depth = Depth+1,即Depth = 1
  3. 重复第2步操作,此时S={C,D,E},此时因为队列S中的节点不都是同级节点,所以Depth的值保持不变,
  4. 重复第2步操作,此时S={D,E,F},此时队列S中同时存在B节点和D节点的子节点,但是因为节点B和节点D是同级节点,所以他们的子节点也是同级节点,即此时队列S中的节点都为同级节点,所以Depth = Depth+1,即Depth = 2
  5. 此时我们从队列中取出的元素为D,D节点的子元素有节点B和E,因为节点B已经遍历过,并且已经出了队列,而节点E当前正在队列中,所以不需要重复添加,即S={E,F},虽然此时队列S中的节点为同级节点,但是在步骤4中我们已经对节点E和节点F的同级节点进行过一次累加,所以此时Depth不需要改变
  6. 重复第2步操作,此时出队列的为节点E,节点E的子元素有节点B,D,F,G,这四个子节点中除了节点G还没有遍历过,并且不再队列S中,所以只需要将节点G加入队列S即可,S={F,G},此时队列S中的节点不都是同级节点,所以Depth不变
  7. 再次从队列中取出一个元素,即元素F,节点F的所有子节点都已经遍历过,所以不需要改变队列S,即S={G},此时队列S中的元素为同级元素,所以Depth = Depth+1,即Depth = 3
  8. 再次从队列中取出一个元素,即元素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开始,将所有节点按照深度优先遍历的方式输出

算法步骤:

  1. 首先查找节点A的子节点,A的子节点有节点B和节点C
  2. 选择节点A的子节点B,继续查询节点B的子节点,节点B的子节点有节点D和节点E
  3. 选择节点B的子节点D,继续查找节点D的子节点,节点D的子节点有节点B和节点E;
  4. 选择节点D的子节点,因为节点D的子节点B在第3步中已经访问(已访问过的节点不能重复访问),所以我们选择节点E;
  5. 节点E的子节点有节点F和节点G,选择子节点F,继续查找子节点F的子节点,节点F有子节点C;
  6. 选择节点F的子节点C,继续查询节点C的子节点,节点C的子节点只有节点A,但是节点A已经访问过了,所以此时我们需要向上回溯
  7. 回溯到节点F,节点F除了子节点C外,在没有其他子节点,所以继续向上回溯;
  8. 回溯到节点E,节点E除了子节点F还有子节点G,并且节点G没有被访问过,所以此时我们选择节点G,继续查询节点G的子节点;
  9. 因为节点G已经是叶子节点,所以我们继续向上回溯;
  10. 回溯到节点E,但是节点E的两个子节点F和G。都已经被访问过了,所以继续向上回溯;
  11. 回溯到节点D,发现节点D的两个子节点都已经被访问过,继续向上回溯;
  12. 回溯到节点B,节点B的两个子节点也是都被访问过了,继续向上回溯;
  13. 回溯到节点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);
			}

		}
	}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值