同模BFS+DFS in Tree and Graph 2020-03-29(未经允许,禁止转载)

BFS

BFS基于队列,层层遍历每个节点只访问了一次,时间复杂度O(N)

  • 关于Tree的BFS:
    首先root节点入队,然后将当前批次队列中的节点出队,并将它们的孩子入队
    关于二叉树的层次遍历模板如下
# 使用【队列】进行层次遍历
class Solution:
    def levelOrder(self, root: TreeNode) -> List[int]:
        res = list()

        # 如果树根存在,那么才开始层次遍历       
        if root:
            queue = list()
            queue.append(root)
            # 核心代码开始
            while queue:
                l = len(queue)
                for i in range(l):
                    # 注意,这里是不断pop首个元素
                    out = queue.pop(0)
                    # 出队列,将val加入结果集
                    res.append(out.val)
                    # 将出队元素的合法孩子们入队
                    # 如果有左孩子
                    if out.left:
                        queue.append(out.left)
                    # 如果有右孩子
                    if out.right:
                        queue.append(out.right)
        return res

notes:因为实现层次遍历是利用了队列不断弹出队首元素,这里使用list.pop(0)来实现,时间复杂度较高,弹出一次后面所有元素都得前移一位

  • 关于图的BFS:
    与Tree的BFS大同小异,主要2个小区别:
    1、tree只有1个root作为BFS的源点,而图可以有多个源点,所以首先需要把多个源点都入队;或者认为图存在一个虚root,这些源点都是虚root的孩子
    2、tree结构不存在回路,不需要标志某个节点是否访问过,但图必须标志节点是否已经被访问过。【可以额外使用字典/列表登记,但更巧的是直接原地修改元素值进行标记】

例题1
一个 N x N 的grid,每个元素要么是1要么是0,1表示陆地,0表示海洋,返回距离陆地最远的海洋到离它最近的陆地区域的距离。这里的所有距离指曼哈顿距离。如果全是1或者0,返回-1

思路:这是一个图的BFS,所有的1可以组成源点集,每次向外扩散,最后被扩散到的海洋就是最远的海洋。扩散过程中维护一个变量step表示曼哈顿距离

class Solution:
    def maxDistance(self, grid: List[List[int]]) -> int:
        m = len(grid)
        n = len(grid[0])
        position = list()
        # 先求出所有的陆地位置,加入队列position
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    position.append((i, j))
                    
        # 全部是陆地或者海洋
        if not position or len(position) == m*n:
            return -1
        
        ## 下面开始层次遍历 ##
        # 多块陆地按上下左右四个方向向海洋深处扩散,最后被扩散到的那片海洋就是目标海洋,扩散需要的步长就是曼哈顿距离
        
        # 字典visited_d用于登记节点是否被访问过
        visited_d = dict()
        step = 0
        while position:
            step += 1
            l = len(position)
            for i in range(l):
                # 节点出队
                p = position.pop(0)
                # 孩子入队
                x, y = p[0], p[1]
                for child in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]:
                    # 如果孩子没有被扩散到
                    if 0 <= child[0] < m and 0 <= child[1] < n and child not in visited_d:
                        # 登记
                        visited_d[child] = 1
                        # 入队
                        position.append(child)
        return step-1

优化:上面的方法中,使用字典visited_d登记节点是否被访问过,额外开辟了空间,可以原地修改grid表示当前位置与1的距离

class Solution:
    def maxDistance(self, grid: List[List[int]]) -> int:
        m = len(grid)
        n = len(grid[0])
        position = list()
        # 先求出所有的陆地位置,加入队列position
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    position.append((i, j))
                    
        # 全部是陆地或者海洋
        if not position or len(position) == m*n:
            return -1
        
        ## 下面开始层次遍历 ##
        # 多块陆地按上下左右四个方向向海洋深处扩散,最后被扩散到的那片海洋就是目标海洋,扩散需要的步长就是曼哈顿距离
        
        # 使用字典visited_d登记节点是否被访问过 -> 原地修改grid进行登记
        # visited_d = dict()
        step = 0
        while position:
            step += 1
            l = len(position)
            for i in range(l):
                # 节点出队
                p = position.pop(0)
                # 孩子入队
                x, y = p[0], p[1]
                for child in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]:
                    # 如果孩子没有被扩散到
                    if 0 <= child[0] < m and 0 <= child[1] < n and grid[child[0]][child[1]] == 0:
                        # 原地修改grid实现登记
                        grid[child[0]][child[1]] = step
                        # 入队
                        position.append(child)
        return step-1

其他图解说明可以参考:https://leetcode-cn.com/problems/as-far-from-land-as-possible/solution/jian-dan-java-miao-dong-tu-de-bfs-by-sweetiee/

DFS

DFS基于栈,也是每个节点只访问了一次,时间复杂度O(N)

巧的是,我们只需在层次遍历的基础上改动【一处】,即改变pop元素的位置,就实现了BFS到DFS的转变

栈总是弹出最后一个元素,而队列总是弹出队首元素,因此将pop(0)改为pop()即可

# 使用【栈】进行深度优先遍历
class Solution:
    def DFS(self, root: TreeNode) -> List[int]:
        res = list()

        # 如果树根存在,那么才开始深度优先遍历       
        if root:
            stack = list()
            stack.append(root)
            # 核心代码开始
            while stack:
                
                # 注意,这里是不断pop最尾元素来模拟栈
                out = stack.pop()
                # 出队列,将val加入结果集
                res.append(out.val)
                # 将出队元素的合法孩子们入队。注意,如果按左孩子-右孩子顺序入栈,由于后进先出原则,右孩子所在的子树会优先访问
                # 要实现前序遍历的话,按右孩子-左孩子的顺序入栈即可
                # 如果有左孩子
                if out.left:
                    stack.append(out.left)
                # 如果有右孩子
                if out.right:
                    stack.append(out.right)
        return res


leetcode200岛屿数量
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围

思路:res为函数返回值,初始化为0。遍历整个grid,如果发现是1,说明此处有岛屿,res自增1,然后进行dfs,每访问一个为'1'的结点,将其置为2以标记该位置已访问

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid:
            return 0
        m = len(grid)
        n = len(grid[0])
        res = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == '1':
                    res += 1
                    # dfs
                    self.dfs(grid, i, j, m, n)
        return res
    
    def dfs(self, grid, i, j, m, n):
        stack = list()
        stack.append((i, j))
        while stack:
            l = len(stack)
            for _ in range(l):
                p = stack.pop()
                # 标记该位置已访问
                grid[p[0]][p[1]] = 2
                # 孩子
                for a,b in [1,0],[0,1],[-1,0],[0,-1]:
                    P = p[0]+a
                    Q = p[1]+b
                    if 0 <= P < m and 0 <= Q < n:
                        if grid[P][Q] == '1':
                            stack.append((P, Q))

注意:

与BFS同源的DFS只适合于做前序遍历,而不能模拟中序遍历和后序遍历

非递归的bfs和dfs解决同一问题:翻转二叉树

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        # # bfs翻转-一层一层翻
        # if not root:
        #     return root
        
        # ori_root = root
        # queue = []
        # queue.append(root)
        # while queue:
        #     l = len(queue)
        #     for i in range(l):
                
        #         p = queue.pop(0)
        #         if p is not None:
        #             # 交换
        #             temp = p.left
        #             p.left = p.right
        #             p.right = temp
        #             queue.append(p.left)
        #             queue.append(p.right)
        # return ori_root

        # dfs翻转
        if not root:
            return root
        
        ori_root = root
        queue = []
        queue.append(root)
        while queue:    
            p = queue.pop()
            if p is not None:
                # 交换
                temp = p.left
                p.left = p.right
                p.right = temp
                queue.append(p.left)
                queue.append(p.right)
        return ori_root

        
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容