图的遍历
本文作者 Mr.赵
Email: lovelyalan@foxmail.com
原创文章,转载标明作者,部分图片素材来自网络
从图中某一顶点出发遍历图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历。
本文暂未代入各个编程语言的实现代码,主要目的为理解原理
一.深度优先遍历(DFS)
1.概念
一直向前冲,直到世界尽头
从图中一个未访问的顶点 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。
2.动图演示
此处需要你有 栈(Stack) 基础
可以看到,网页模拟了深度搜索时二叉树和栈的动态变化过程:
- 二叉树中当前遍历到的节点会变成红色;
- 栈中压入 (push)的节点为灰色;
- 栈中弹出 (pop) 的节点会变为红色, 然后消失;
二.广度优先遍历(BFS)
1.概念
如涟漪向外扩散
指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点。
2.动图演示
此处需要你有 队列 基础
可以看到,网页模拟了广度搜索时二叉树和队列的动态变化过程:
- 二叉树中当前遍历到的节点会变成黄色;
- 队列中入队 (put)的节点为黄色;
- 队列中出队 (take) 的节点背景是 蓝色, 代表已经遍历过的值;
三.总结
1.相同点
我们不难发现**,BFS 先遍历了2和4**,才去遍历了2下面的5和6
而DFS就会 遍历2->5->6再去遍历4
2.不同点
深度搜索会在一条路走到尽头,然后返回分叉口,在新的路再走到尽头,循环此过程,直到没有分叉口可走了,代码实现起来比较简单
深度优先就是,从初始点出发,不断向前走,如果碰到死路了,就往回走一步,尝试另一条路,直到发现了目标位置。这种不撞南墙不回头的方法,即使成功也不一定找到一条好路,但好处是需要记住的位置比较少。
广度搜索每次会在每个分叉口后面的路都先走一步,然后接着向前走,有了新的分叉口就同理,直到所有路都走完了,代码实现起来比较困难.
广度搜索属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
3.总结
效率: 深度优先搜索遍历的位置比较少,而广度优先搜索会遍历所有位置,所以效率上肯定是深度优先搜索更胜一筹
准确: 在有多个结果的情况下,深度优先搜索可能返回的结果不是最优解,广度优先搜索因为得到了所有的数据,可以结果最优解上广度优先搜索更胜一筹
没有最好的算法,只有合适的场景,适当的选择适合自己使用场景的算法即可.
四.案例实战
1.题目
来自 LeetCode
钥匙和房间
有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,…,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。
在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i] [j] 由 [0,1,…,N-1] 中的一个整数表示,其中 N = rooms.length。 钥匙 rooms[i] [j] = v 可以打开编号为 v 的房间。
最初,除 0 号房间外的其余所有房间都被锁住。
你可以自由地在房间之间来回走动。
如果能进入每个房间返回 true,否则返回 false。
示例 1:
输入: [[1],[2],[3],[]]
输出: true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。示例 2:
输入:[[1,3],[3,0,1],[2],[0]]
**输出:**false
**解释:**我们不能进入 2 号房间。提示:
1 <= rooms.length <= 1000
0 <= rooms[i].length <= 1000
所有房间中的钥匙数量总计不超过 3000。
2.代码实现
这里是Java的,其他语言爱好者可以 LeetCode 的自己做一下
class Solution {
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
boolean[] seen = new boolean[rooms.size()];//记录每一个房间是否开启
seen[0] = true;//题目中说 0 号房间是打开的
Stack<Integer> stack = new Stack();//栈
stack.push(0);//用0做头节点
while (!stack.isEmpty()){//只要还有路走就不停下来
for (int x : rooms.get(stack.pop())){//遍历这条路下的每一个节点
if (!seen[x]){//如果这个节点在seen数组中为false
seen[x]=true;//就更新它的值
stack.push(x);//并且作为一个新的分叉路存到栈中
}
}
}
for (int i = 0; i < seen.length; i++) {//如果这个存房间是否开启的数组中有一间是关闭的 就结束程序并返回false
if (!seen[i])return false;
}
return true;//如果程序没有被上面的for给结束掉,就说明它所有的门都打开了
}
}
思维泡泡:
你能想到这是 深度 还是 广度吗?