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实战

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