BFS实战

作者: 小碧小琳 | 来源:发表于2018-09-03 17:05 被阅读6次

一、BFS三个步骤:

  • 1、将初始点(一个或多个)加入队列
  • 2、从队列头取出点,判断初始点的周边点,将符合条件的点加入队列尾部
  • 3、重复2操作,直至队列为空。while操作,(一般每个点只加入队列一次)

二、BFS的三个小栗子

遍历树,词梯问题(Word Ladder),被包围的岛屿(Surrounded Islands)

2.1、树

对于树,不需要判重,代码简单。

def bfs_tree(node):
    if node is None:
        return None
    else:
        queue = []
        #用list实现队列,左边是尾,右边是头。
        # 用insert方法把后入的元素插入最左边,入队尾部,用pop的方法弹出最右边
        #保证了先进先出的顺序
        queue.insert(0,node)
        #队列不为空,就继续执行循环
        while queue:
            cur = queue.pop()
            print(cur.val)
            #这里我定义的node的next属性,如果没有子节点,那么next就是空列表。
            for next in cur.nexts:
                queue.insert(0,next)

2.2、Word Ladder

对于图,就需要用一个hashSet判重了。这里的hashSet可以是set,也可是dict,但是不能是list!

2.2.1、为什么BFS找到的路线就是最短的----用图说明

对于一个词梯问题,在我们得到词的 集合以后,需要构造一个图。然后对图进行搜索操作。
参考以前写过的文章理解动态规划、BFS和DFS

引入Python的graph类,vertex类。这个类都有其属性。
用两种方法构造词梯的邻接表

  • 一种是,对每个单词都和其余的所有单词做一次比较,满足词梯条件的对两个点用addedge方法添加一条边。但是时间复杂度过高。
  • 一种, 效率比较高,词桶法。分两步。1、对于新来一个单词,我就先用这个单词的每个缺失一个字母,作为key,然后将满足条件的都添加到该桶中去。2、遍历完单词以后,对同一个桶中的单词进行操作,增加边。
    比如1、在遍历单词时遇到了pope这个单词,我先把ope,p_pe,po_e,pop作为4个key,然后把满足条件的都装到桶里去。再次遇到单词rope时,因为已经有_ope这个key了,就把rope放到桶中,不需要和别的每个单词再次进行比较了。2、接下来,对每个桶中的单词添加边,就会得到图了。

若是把上面的 图画出来,就是这样的:


用BFS进行搜索操作时,从fool到sage。首先,BFS会搜索与fool邻接的点[foul,foil,cool,pool],在第二步,从foul搜索到foil时,发现foil已经存在于visited集合中了,说明有更短的路线可以从fool到foil(只要1步)。
这就说明,BFS找到的路线,一定是最短的。

2.2.2、LeetCode中的127,词梯问题

因为LeetCode中没有graph与vertex类,所以构造不了图。那怎样体现词与词之间的连词呢?比如上图中怎么去搜索fool相邻的四个词呢?

这里用三步去搜索:
1、对于fool的每个字母,
2、都用26个英文字母去替换,
3、再判断替换一个字母以后的单词,是不是存在于总的单词集合中。

比如对于fool,先对第一个字母 f 进行替换,从a到z,aool不存在于总单词集合中,cool,pool存在于总单词集合中,若是这个单词也不存在于visited中,那么就是有效的。

代码实现:

class Solution:
    def ladderLength(self, beginWord, endWord, wordList):

        if len(wordList) == 0:
            return 0
        if endWord not in wordList:
            return 0
        wordSet = set(wordList)
        word_len = len(beginWord)
        len_dict = {}
        len_dict[beginWord] = 1
        queue = []
        queue.insert(0,beginWord)

        while queue:
            cur_word = queue.pop()
            for i in range(word_len):
                for j in range(26):
                    alp = chr(j + ord('a'))
                    new_word = cur_word[0:i] +alp + cur_word[i+1:]
                    #若是等于目标单词,那就返回当前点的深度加1
                    if new_word == endWord:
                        return len_dict[cur_word] + 1
                    if new_word in wordSet and new_word not in len_dict:
                        queue.insert(0,new_word)
                        len_dict[new_word] = len_dict[cur_word] + 1
        #循环结束,仍是没有返回值,说明没有到达目标单词的路径,于是返回0
        return 0

注意这里用的是wordSet是一个集合,如果把visited的单词放到一个列表list中,因为Python中list的in方法时间复杂度很高,就会超时。

2.3、二维数组问题——LeetCode、200.Surrounded Islands

在上一篇文章中也讲过,对于数组问题,因为能够确定图的形状, 所以不一定非要用一个Set来保存已经搜索过的点。可以用一些小技巧,来更快速的实现。

比如这一题,先搜索数组的四条边界,可以把已经搜索过,下次遇到不会再进行搜索的“O”,替换成别的字母“+”,这样下次就不会再进行搜索了。

利用BFS把四条边的“O”都变成“+”以后,再把数组里面的被X包围的O都替换成X,再把“+”换回“O”,就能解决问题了。

需要注意,在Python中,set类型中,是不允许添加进list类型的元素的。
比如,set.add([i,j])就会出错,而set.add((i,j))就是正确的。在添加坐标时,需要注意这一点。

代码实现:

def solve(board):
    """
    :type board: List[List[str]]
    :rtype: void Do not return anything, modify board in-place instead.
    """

    ####
    #这里需要注意的是,因为采用的方式,是如果遇到O,就替换成+,
    # 因此不会存在再次遇到相同的O被访问的情况,因此不用添加visited了。
    #根那个词梯问题类似,不需要额外的visited,重要的不是这个集合,而是判断是否重复的步骤
    ####

    if len(board) == 0:
        return

    m = len(board)
    n = len(board[0])

    #定义一个bfs函数,传入点的索引i,j,
    #1、把满足条件的是’O‘的点都变成’+‘/2、并且将location添加进queue中
    #locat 是一个(i,j)元组,用以放入visited中的,因为list不能放入set中,元组能。
    def bfs(i,j):
        queue = []
        #如果locat的点是O,就变成+,并将locat添加进queue中

        if board[i][j] == 'O':
            board[i][j] = '+'
            queue.insert(0,(i,j))
        #若是该点不是’O,那这个函数就结束了。
        while queue:
            new_locat = queue.pop()
            i = new_locat[0]
            j = new_locat[1]
            #并对该点周围四个location判断是否有效且是‘O’,如果是,就变为‘+’,并且添加到queue中
            for i_new,j_new in [(i-1,j),(i+1,j),(i,j-1),(i,j+1)]:
                if i_new >= 0 and i_new <= m-1 and j_new >= 0 and j_new <= n-1 and \
                        board[i_new][j_new] == 'O':
                    board[i_new][j_new] = '+'
                    queue.insert(0,(i_new,j_new))

    #对四条边界的每个点,进行上述的bfs操作
    for i in range(m):
        #第一列
        bfs(i,0)
        print(board)
        #最后一列
        bfs(i,n-1)

    for i in range(n):
        #第一行
        bfs(0,i)
        #最后一行
        bfs(m-1,i)

    #四条边为O的都已经替换为+了,只需要把里面的被X包围的O替换成X,
    #把外面的没被包围的+还原为O,即可满足题意了
    for i in range(m):
        for j in range(n):
            if board[i][j] == 'O':
                board[i][j] = 'X'
            if board[i][j] == '+':
                board[i][j] = 'O'

print(solve([["O","O"],["O","O"]]))

相关文章

  • BFS实战

    一、BFS三个步骤: 1、将初始点(一个或多个)加入队列 2、从队列头取出点,判断初始点的周边点,将符合条件的点加...

  • 走迷宫(BFS例题)

    BFS例题---走迷宫 BFS对应数据结构queue BFS算法时间复杂度O(n^2) BFS算法不走走过的点 的...

  • 什么时候用BFS,什么时候用DFS

    参考什么时候用dfs,什么时候用bfs 参考什么时候用DFS,什么时候用BFS? BFS的缺点适用情况 BFS的一...

  • Binary Tree(2)

    BFS vs DFS for Binary Tree What are BFS and DFS for Binar...

  • BFS/DFS python模板与实现

    BFS BFS在搜索完第k层的节点之前,是不会搜索第k+1层的节点的。 BFS原理 BFS所用的是队列。把每个还没...

  • Chapter12—搜索—广搜

    1. 题目列表 POJ3126(BFS) POJ3087(BFS) POJ3414(BFS+路径打印) 2. 广搜...

  • BFS及其应用

    内容概要: BFS类的实现 BFS求解连通分量 BFS求解无向图点对之间的一条最短路径 BFS判定无环图和二分图 ...

  • 8.19 - hard - 64

    317 . Shortest Distance from All Buildings 典型的BFS的题目,BFS可...

  • H - 8 HDU - 1495

    BFS

  • BFS与DFS总结

    BFS判断连通性 DFS判断连通性 BFS最优解(不走重复路径) BFS最优解(走重复路径) DFS回溯(不走重复...

网友评论

    本文标题:BFS实战

    本文链接:https://www.haomeiwen.com/subject/ofzbwftx.html