宽度优先搜索 — 习题总结

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中

剑指 Offer II 114. 外星文字典

要点:可以通过比较字符串列表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并不存在映射关系中,则返回指定的默认值
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值