BFS

2021/12/05 Algorithms 共 3050 字,约 9 分钟

常规BFS

  • 每次把周围未访问的点加进队列
  • 在append时检查visit标记和设置visit标记
  • 只能解决长度为1的问题

也可以在queue pop时再check 是否visit和标记visit,时间复杂度加边数。

题目:

从外向内扩展

双向BFS

双向bfs模板参考

常规bds可能会搜索空间爆炸, 假如同时从两个方向开始搜索,一旦搜索到相同的值,意味着找到了一条联通起点和终点的最短路径。

双向 BFS」的基本实现思路如下:

  1. 创建「两个队列」分别用于两个方向的搜索;
  2. 创建「两个哈希表」用于「解决相同节点重复搜索」和「记录转换次数」;
  3. 为了尽可能让两个搜索方向“平均”,每次从队列中取值进行扩展时,先判断哪个队列容量较少;
  4. 如果在搜索过程中「搜索到对方搜索过的节点」,说明找到了最短路径。
d1、d2 为两个方向的队列
m1、m2 为两个方向的哈希表,记录每个节点距离起点的
    
// 只有两个队列都不空,才有必要继续往下搜索
// 如果其中一个队列空了,说明从某个方向搜到底都搜不到该方向的目标节点
while(!d1.isEmpty() && !d2.isEmpty()) {
    if (d1.size() < d2.size()) {
        update(d1, m1, m2);
    } else {
        update(d2, m2, m1);
    }
}

// update 为将当前队列 d 中包含的元素取出,进行「一次完整扩展」的逻辑(按层拓展)
void update(Deque d, Map cur, Map other) {}
# 单词接龙: https://leetcode.cn/problems/word-ladder/
class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        word_set = set(wordList)
        word_set.add(beginWord)
        import collections
        graph = collections.defaultdict(list)
        for w in word_set:
            wl = list(w)
            for i in range(len(wl)):
                old = w[i]
                for k in range(ord('a'), ord('z') + 1, 1):
                    c = chr(k)
                    if c == old: continue
                    wl[i] = c
                    nxt = "".join(wl)
                    if nxt in word_set:
                        graph[w].append(nxt)
                    wl[i] = old
        # print(graph)
        import collections
        ans = float("inf")

        ma, mb = {}, {}
        ma[beginWord] = 0
        mb[endWord] = 0
        q1 = collections.deque()
        q1.append(beginWord)
        q2 = collections.deque()
        q2.append(endWord)

        def update(q: collections.deque, cur: map, other: map):
            for _ in range(len(q)):
                pop = q.popleft()
                for nxt in graph[pop]:
                    if nxt in cur: continue
                    cur[nxt] = cur[pop] + 1
                    q.append(nxt)
                    if nxt in other:
                        nonlocal ans
                        ans = min(ans, cur[nxt] + other[nxt])

        while q1 and q2:
            if len(q1) < len(q2):
                update(q1, ma, mb)
            else:
                update(q2, mb, ma)
        return ans + 1 if ans < float('inf') else 0

题目:

优先队列BFS

dijkstra

题目:

0-1 广度优先搜索

参考:https://leetcode-cn.com/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/solution/shi-wang-ge-tu-zhi-shao-you-yi-tiao-you-xiao-lu-2/

为了可以处理权值为0的边,使用双端队列,当遇到是0的边,就加入到队首。

保证广度优先搜索正确性的基础在于:对于源点 ss 以及任意两个节点 uu 和 vv,如果 \textit{dist}(s, u) < \textit{dist}(s, v)dist(s,u)<dist(s,v)(其中 \textit{dist}(x, y)dist(x,y) 表示从节点 xx 到节点 yy 的最短路长度),那么节点 uu 一定会比节点 vv 先被取出队列。在常规的广度优先搜索中,我们使用队列作为维护节点的数据结构,就保证了从队列中取出的节点,它们与源点之间的距离是单调递增的。

0-1 广度优先搜索的实现其实与 Dijkstra 算法非常相似。在 Dijkstra 算法中,我们用优先队列保证了距离的单调递增性。而在 0-1 广度优先搜索中,实际上任意时刻队列中的节点与源点的距离均为 dd 或 d + 1d+1(其中 dd 为某一非负整数),并且所有与源点距离为 dd 的节点都出现在队首附近,所有与源点距离为 d + 1d+1 的节点都出现在队尾附近。因此,我们只要使用双端队列,对于边权为 00 和 11 的两种情况分别将对应节点添加至队首和队尾,就保证了距离的单调递增性。

实现:

  • 为了可以处理权值为0的边,使用双端队列,当遇到是0的边,就加入到队首。
  • 每次pop时检查是否visit,如果是visit就continue跳过,否则接着处理,并设置visit为true,这个和dijstra类似,和常规bfs(append时检查visit和设置visit)有区别
  • 只能解决0/1的长度问题。

示例题目

class Solution:
    def minimumObstacles(self, grid: List[List[int]]) -> int:
        '''
        dijstra, pop时已经是最短成本了,以成本作为优先策略
        :param grid:
        :return:
        '''
        m, n = len(grid), len(grid[0])
        if m == 1 and n == 1:
            return 0
        q = collections.deque()
        if grid[0][0] == 1:
            q.append((1, (0, 0)))
        else:
            q.append((0, (0, 0)))
        dist = {}
        while q:
            cost, u = q.popleft()
            if u in dist: continue
            dist[u] = cost
            x, y = u
            if x == m - 1 and y == n - 1:
                return cost
            for dx, dy in Directions.dirs:
                nx, ny = x + dx, y + dy
                if nx < 0 or nx >= m or ny < 0 or ny >= n:
                    continue
                v = (nx, ny)
                if v in dist:
                    continue
                if grid[nx][ny] == 1:
                    q.append((cost + 1, v))
                else:
                    q.appendleft((cost, v))
        return -1

题目列表:

Search

    Table of Contents