1. BFS摘要
1)基本思路:使用队列 (Queue) 的数据结构来进行BFS,把待访问节点放入队列中。每当遍历并弹出节点时,都要判断它的子节点 / 邻居节点,若存在,则将其子节点 / 邻居节点也放入队列中。注:在BFS图时,每当把节点放入队列时,都要标记该节点被访问。
2)BFS的使用场景:连通块问题 (Connected Component),分层遍历 (Level Order Traversal),拓扑排序 (Topological Sorting)
3)图的BFS
- 如果图中存在环,则同一个节点可能重复进入队列
- 适用哈希表去重:HashMap / HashSet
- 树是特殊的图,没有环的存在,因此BFS树时通常不需要去重操作
4)BFS通用模版 (Java)
Queue<Node> queue = new ArrayDeque<>();
Map<Node,Integer> distance = new HashMap<>();
//step1
//把初始节点放入queue里
//distance有两个作用,一是判断节点是否已经访问,二是记录离起点的距离
queue.offer(node);
distance.put(node,0);
//step2
//不断访问队列queue中的节点
while(!queue.isEmpty()){
Node cur = queue.poll();
//step3
//拓展出相邻节点
for(Node neighbor : cur.neighbors){
if(distance.containsKey(neighbor)){
continue;
}
queue.offer(neighbor);
distance.put(neighbor,distance.get(cur)+1);
}
}
2. ArrayDeque介绍
1)ArrayDeque简介
- ArrayDeque是Deque接口的一个实现,是Java集合中双端队列的数组实现。
- 底层使用数组来存储元素,可扩容,线程不安全,不支持null值。
- 可以作为栈来使用,效率高于Stack;也可以作为队列来使用,效率高于LinkedList。
2)ArrayDeque的初始化
//ArrayDeque有3种构造函数来初始化。除了无参的构造函数使用默认容量,其他两种构造函数都使
//用allocateElements来计算分配初始容量
public ArrayDeque() {
elements = new Object[16];
}
public ArrayDeque(int numElements) {
allocateElements(numElements); //需要的数组长度大小
}
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size()); //根据给定的集合分配数组的大小
addAll(c); //将集合中的元素放到数组中
}
3)ArrayDeque的常用方法
ArrayDeque作为队列(FIFO)使用时的方法
队列方法 | 等效的双端队列方法 |
---|---|
add(e) | addLast(e) |
offer(e) | offerLast(e) |
remove() | removeFirst() |
poll() | pollFirst() |
element() | getFirst() |
peek() | peekFirst() |
ArrayDeque作为栈(FILO)使用时的方法
栈方法 | 等效的双端队列方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
3. 相关例题
1)简单BFS
LeetCode 133. 克隆图
思路:先找到并复制所有的点,将它们放在一个哈希表HashMap中;然后根据这个哈希表HashMap复制所有的边。尽量保证代码的低耦合
LeetCode 127. 单词接龙
要点:给出两个单词start和end,和一个字典,求从start到end的最短转换序列长度;
每次变换只能改变一个字母;变换过程的中间单词必须在字典中出现。
思路:使用BFS从start开始,逐层向外扩张,直到遍历到end或带访问队列queue为空时为止。一种方法是枚举字典中的单词,与当前queue弹出的单词比较是否仅有一个字母不同,时间复杂度为O(N*L)。另一种方法是,枚举26个字母,替换掉当前单词的各字母,判断新得到的单词是否在字典中,时间复杂度为O(26*L*L)。
N是字典中的单词个数,L为单词中的字母个数。因而当字典较大时,第二种方法的时间复杂度较低。
需要注意的是,当用一个字符数组构建新的字符串时,需要的时间为O(N)。
2)矩阵中的BFS
LeetCode 200. 岛屿数量
要点:由于坐标包含x和y两个变量,因此java中需要写一个类,包含这两个属性。
矩阵相当于特殊的图,矩阵中每个点的邻居通常是其上下左右四个方向。
思路:
- 特殊情况处理:返回输入为空或数组中没有元素的返回值
- 需要创建一个visited数组标明被访问过的节点
- 当该点是陆地并且未被访问过,对该点进行bfs
- bfs里判断拓展的新点是否有效,最好单独另写一个调用方法,降低代码耦合。
LintCode 630 · 骑士的最短路径II
思路:
- 根据题意,创建一个二维数组存放偏移量;存放带访问点的队列queue;标明点是否被访问的二维数组 / 哈希表HashMap visited
- 将二维坐标转换为一维坐标的小技巧:行坐标*列数 + 列坐标
3)拓扑排序
如何定向:图 + 有依赖关系 + 有向 + 无环 ==> 拓扑排序
入度 (In-degree) : 有向图中指向当前节点的点的个数 (或指向当前节点的边的条数)
算法:
- 统计每个点的入度
- 将每个入度为0的点放入队列queue中,作为起始节点
- 不断从队列queue中拿出一个点,去掉这个点的所有连边,其他点的相应入度-1
- 一旦发现新的入度为0的点,放入队列
LeetCode 剑指 Offer II 113. 课程顺序
要点:int值表示总课程数numCourse,二维数组表示先修顺序 prerequisites
思路:
- 首先要建立图graph,用来表示每门课及其所指向的其他课程,应为元素为List类型对象的数组,注意需要给数组的每个下标new一个list。
- 声明一个int类型数组来存放每门课程的入度
- 根据prerequisites,来构建图的内容,和统计每门课的入度
- 将入度为0的点作为起始点放入队列中;用一个变量courseChoose来记录所选的课程数;声明一个int数组来存放所选课程
- 典型的拓扑排序算法,直到queue为空结束
- 通过比较courseChoose和numCourse来返回结果
LintCode 605 · 序列重构
要点:二维数组seqs里存放的就是序列中数的顺序,可能会有重复
思路:
- 首先根据seqs里的序列构建一个图graph,图graph需要收录序列中所有的点。图的类型是Map<Integer,Set<Integer>>,即key为一个数,value是这个数指向的其他数字的集合
- 根据图graph来统计每个点的入度
- 初始化一个队列queue,将入度为0的点放入队列中,并且放入一个list来表示拓扑序
- bfs队列中的点,若当前队列size大于1,直接将list赋值为null,即不存在唯一序列
- 否则,弹出队列当前点,根据graph遍历该点的邻居,并将其邻居的入度都-1
- 若有邻居的入度为0,则将该邻居加入队列,并添加到表示拓扑序的list中
要点:可以通过比较字符串列表words中,相邻两个单词的顺序,来获得字符顺序
思路:
- 根据字符串列表里的字符串,来构建图graph,类型为Map<Character,Set<>>。key就是一个字符,value是排序在该字符后面的字符集合。具体思路就是先遍历字符串列表和每个字符串,将列表中的每个字符添加到哈希表中,并且创建一个HashSet集合;通过比较字符串列表words里相邻的字符串,来得到一对字符的顺序,将该顺序添加到哈希表中。
- 根据构建的图graph,来统计每个字符的入度indegrees,类型为Map<Character,Integer>。key是一个字符,value是该字符的入度。
- 根据图graph和入度indegrees,将入度为0的字符放入队列queue中。若队列不为空,弹出一个字符current,根据graph找到该字符current后面的字符,将他们的入度-1;若有入度为0的字符,则将其加入队列。
- 若最后得到的表示字典序的字符串长度 != 图graph的大小,则说明graph中有闭环,导致某些字符的入度永远不会为0,此时字典须返回""。否则正常返回字典序字符串。
- 特殊情况:在构建图graph的时候要考虑["abc","ab"]这种不合法的情况,即对应位字符相同,前面的单词长度比后面的大,此时图的返回值为null
- 有向图 + 依赖关系 + 无环,定向该题为拓扑排序
4. 拓展
1)new String(数组) & ch.toString()
ch.toString()返回了一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示。
new String(数组) 则是将字符数组转化为字符串
2) hashMap.getOrDefault(Object key, V defaultValue)
- hashMap是HashMap类的一个对象
- key - 键
- defaultValue - 当指定的key并不存在映射关系中,则返回指定的默认值