回溯dfs,bfs模板总结

做下笔记:

BFS和DFS其实是两种不同遍历图的方式,前者是一层一层遍历,正是因为这个特性,它的一个应用就是可以用来找最短路径

后者是一条路走到黑的方式,它的应用就是利用递归进行回溯遍历,得到所有组合情况,下面分开介绍:

注意:1 两者其实都可以用来遍历找到所有路径(组合所有的情况),只不过由于BFS特殊遍历方式,可以用来解决找最短路径这一问题,由于DFS的回溯特性常用来在找所有组合情况这一场景应用

           2个人觉得DFS在实现起来较BFS难理解一点,所以DFS后面多写了几个例子,供深入理解

BFS常用来找最短路径(路径没有权重,考虑权重的话可以使用Dijkstra,bellman等):

可以简单理解为其是按照一层一层往下找的,一旦找到目的节点就结束,即是最短路径,因为其他的路径最少起码再往下走一层或更多层才有可能到达,无疑比当前这条路径长如下fig1,从开始出发后,先在第一层中找,即1,2,3没有找到结束标记,接着从第二层中找4,5,6,7,8,9没有结束标记,接着从第三层找即10,11,12,end 哇!找到了,这时就可以结束了即最短路径就是start-2-6-end,为什么呢?因为尽管接着继续往下一层走还有可能有到达终点的路径类如(start-1-4-11-14-16-end),但层数增多了,路径增多了,我们要的是最短路径所以不给予考虑。

注意,有可能同一层下有多条路径,这时候就最短路径有可能就是多条,类如fig2其实最短路径除了start-2-6-end还有start-3-8-end,但是如果要求只是找到一条最短路径,那么其实一旦找到一条就可以停止了,但如果是找到所有最短路径,务必把当前层遍历完才可结束。

大体步骤就是(这里假设只要找到一条就可以了):

1 首先其将开始节点加入一个队列

2 取出队首节点,判断当前该节点是否是结束节点,如果是,结束while,返回要求的路径信息,如果不是则进行3

扫描二维码关注公众号,回复: 3577617 查看本文章

3 然后将与当前队首节点相邻的(能一步 到达的)所有节点依次放到队列中,放入的时候需要记录路径信息,类如步数(方便最后得出最短路径一共是多少步)、该节点的父节点(方便输出最短路径每一步的具体节点)等等,同时标记该节点已被访问过

BFS找最短路径模板

def bfs(begin,end,List):

    #1 将开始节点放入队列
    queue = [(begin,1)]

    Listtemp = List

    while queue:
            #2弹出队列首部
            temp,lens= queue.pop(0)

            if temp==end:
                #找到终点,返回路径,类如这里只是返回了这条最短路径一共经过了多少步
                return lens
                
            else:
                #3这里遍历和temp相邻的所有节点,具体怎么写因具体环境而变
                for newnode in tempNeighbourNodes:
                 

                    #记录路径信息:每遍历到一个就将其加入到队列中
                    queue.append((newnode,lens+1))

                    #标记已经走过:最后将遍历到的节点从列表中删掉
                    Listtemp.remove(newnode)
               
     return 0

DFS说白了就是一条路走到黑

对应到fig1就是从start开始依次是1,然后找到与1相邻的4,接着找到与4相邻的10,此时发现接下来无路可走了,那么回溯到4的另一个相邻11,接着找到11的相邻13,又发现无路可走了,那么回溯到11的另一个相邻14,接着找到14的相邻15,又发现无路可走了,那么回溯到14的另一个相邻16,接着找到16的相邻end,发现找到结束标志。如果是找到一条路径那么此时就可以返回结束了,如果是找所有可以到达的路径即所有情况(这个是DFS更多的应用场景),那么此时就记录下这条路径信息,不用返回,接着回溯,遍历其他情况,直到所有节点遍历完。

DFS常利用回溯找到所有可能的组合情况模板

def backtrack(dimension):
    #临界判断
    return

    #判断当前维度下的数组是否满足最终解
    if solution[] is well:
        #记录下该条路径
        rembermer solution............

    #遍历当前维度所有情况的值
    for visit(i) in dimensions:
        #如果没有被访问过,且是最终解的一部分,即分步解
        if !flag[visit(i)] and visit(i) is PartWell:

            #标记访问过了的标记
            flag[visit(i)]=true

            #然后在当前维度下递归调用下一步
            backtrack(dimension+1)
            
            #恢复节点为未访问标记
            flag[visit(i)]=False
        
    return 0
                



注意:

1在步骤:遍历当前维度所有情况的值这里可以灵活应用不一定非要是for循环,比如走迷宫这里其实就是上下左右四个步骤,分别写四个dfs就可以了

2有的时候元素可以重复使用,例如在判断括号是否合法的情形下(具体例子在下面)这时候就不必标记访问标记了即下面的这两句话就没必要了

#标记访问过了的标记
flag[visit(i)]=true


#恢复节点为未访问标记
flag[visit(i)]=False

3在使用DFS找一条符合路径的时候即找到就行了,不必找到所有组合情况,那么可以对应的做简单修改:即找到就返回,不必一直回溯下去了

def backtrack(dimension):
    #临界判断
    return False

    #判断当前维度下的数组是否满足最终解
    if solution[] is well:
        #记录下该条路径
        rembermer solution............
        return True

    #遍历当前维度所有情况的值
    for visit(i) in dimensions:
        #如果没有被访问过,且是最终解的一部分,即分步解
        if !flag[visit(i)] and visit(i) is PartWell:

            #标记访问过了的标记
            flag[visit(i)]=true

            #然后在当前维度下递归调用下一步
           if  backtrack(dimension+1):
                return True
            
            #恢复节点为未访问标记
            flag[visit(i)]=False
        return False
        
    return 0
                

举例1:leetcode 77:https://leetcode.com/problems/combinations/description/

Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.

Input: n = 4, k = 2
Output:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
class Solution:
    def combine(self, n, k):
        """
        :type n: int
        :type k: int
        :rtype: List[List[int]]
        """
        l = []
        self.result = []
        self.backtrack(n,k,0,l)
        return self.result
    def backtrack(self,n,k,start,l):
        if k==0:
            self.result.append(l[:])
            return 
        else:
            for i in range(start,n):
                l.append(i+1)
                self.backtrack(n,k-1,i+1,l)
                l.pop(len(l)-1)

举例2:leetcode 78 https://leetcode.com/problems/subsets/description/

Input: nums = [1,2,3]
Output:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]
class Solution:
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        self.res = []
        l = []
        self.dfs(nums,0,l)
        self.res.append([])
        return self.res
    def dfs(self,num,start,l):
        for i in range(start,len(num)):
            l.append(num[i])
            self.res.append(l[:])
            self.dfs(num,i+1,l)
            l.pop(len(l)-1)

以上的例子中第一个元素是多少都无所谓,但是有时候第一个元素是有要求的,那么就要在dfs外层再加一个循环,先找到第一个符合要求的元素,在用dfs:

例子:leetcode 79 https://leetcode.com/problems/word-search/description/

Given a 2D board and a word, find if the word exists in the grid.

The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once.

board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

Given word = "ABCCED", return true.
Given word = "SEE", return true.
Given word = "ABCB", return false.
class Solution:
    def exist(self, board, word):
        """
        :type board: List[List[str]]
        :type word: str
        :rtype: bool
        """
        self.m = len(board)
        self.n = len(board[0])
        self.flag = [[False for col in range(self.n)] for row in range(self.m)]
        for i in range(self.m):
            for j in range(self.n):
                if board[i][j]==word[0]:
                    self.flag[i][j]=True
                    if self.dfs(board,word[1:],i,j):
                        return True
                    self.flag[i][j]=False
        return False
        
    def dfs(self,board,word,i,j):
        if len(word)==0:
            return True
        if  i+1<self.m  and  board[i+1][j]==word[0] and  not self.flag[i+1][j]:
            self.flag[i+1][j]=True
            if self.dfs(board,word[1:],i+1,j):
                return True
            self.flag[i+1][j]=False
        
        if  i>0  and  board[i-1][j]==word[0] and  not self.flag[i-1][j]:
            self.flag[i-1][j]=True
            if self.dfs(board,word[1:],i-1,j):
                return True
            self.flag[i-1][j]=False
        
        if  j+1<self.n and  board[i][j+1]==word[0] and  not self.flag[i][j+1]:
            self.flag[i][j+1]=True
            if self.dfs(board,word[1:],i,j+1):
                return True
            self.flag[i][j+1]=False
            
        if  j>0 and  board[i][j-1]==word[0] and  not self.flag[i][j-1]:
            self.flag[i][j-1]=True
            if self.dfs(board,word[1:],i,j-1):
                return True
            self.flag[i][j-1]=False
        return False
        

猜你喜欢

转载自blog.csdn.net/weixin_42001089/article/details/83001841