BFS · 广度优先搜索

为什么有这篇文章

近半年经常刷题,也参加了同学们自行组织的刷题会,到写这篇文章为止 leetcode 已经 AC 了 77 道题目了。时常的总结是必要的,而分享知识不仅能帮助自己树立知识脉络,更能帮助到大家,为学习算法的同学们提供一种参考的思路。

广度优先搜索概要

广度优先搜索(BFS)是在搜索中首先将所处位置的直接邻居访问一遍,再进入下一层重复之前操作的一种搜索。这种算法需要维护一个队列。队列有着先进先出(First In First Out)的性质。算法的开始一般都是把根节点加入到空队列中,然后开始一个结束条件是队列为空的循环。每一次循环中,算法会取得队列最前的一个元素并且访问它的所有邻居。在访问邻居的同时,程序会把邻居添加到队列中。至此广度优先搜索就写完了,程序会按照上述循环把能够访问到的节点都访问一遍。

这种算法可以应用在图,也可以应用在树上。下面我们来看几道跟树这种数据结构有关的题目。

例题 1. N-ary Tree

leetcode 429 这道题是典型的广度优先搜索,要求按层遍历树。解答如下:

"""
# Definition for a Node.
class Node(object):
    def __init__(self, val, children):
        self.val = val
        self.children = children
"""
class Solution(object):
    def levelOrder(self, root):
        """
        :type root: Node
        :rtype: List[List[int]]
        """
        if root is None:
            return []
        queue = [(root, 0)]
        result = []
        while queue:
            node, level = queue.pop(0)
            while len(result) <= level:
                result.append([])
            result[level].append(node.val)
            for child in node.children:
                queue.append((child, level + 1))
        return result
        

我们规定根节点是第 0 层,然后把节点和所在层级放在一个 tuple 里进队。循环的终止条件是队列为空,当队列有元素时,队头出队,我们对元素做一些处理,然后将它的子孙加入队列。在这个问题中,对于元素的操作比较简单,把它加入结果数组中即可。当然,加入之前要保证数组对应的位置有数组存在(结果是一个包含数组的数组),不然会抛出索引越界的异常。

例题 2. 二叉树上的交叉遍历

leetcode 103 是一道中等难度的题目,要求交叉遍历二叉树。交叉的意思是从根节点开始,根节点那层从左到右遍历,下一层从右到左,再下一层从左到右,依次循环直到最后一层,期间忽略 null.

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

class Solution(object):
    def zigzagLevelOrder(self, root):
        """
        :type root: TreeNode
        :rtype: List[List[int]]
        """
        if root is None:
            return []
        queue = [(root, 0, True)]
        result = []
        while queue:
            node, level, left_to_right = queue.pop(0)
            while len(result) <= level:
                result.append([])
            nodes_on_same_level = [node]
            while len(queue) > 0 and queue[0][1] == level:
                nodes_on_same_level.append(queue.pop(0)[0])
            for n in nodes_on_same_level:
                if n.left:
                        queue.append((n.left, level + 1, not left_to_right))
                if n.right:
                    queue.append((n.right, level + 1, not left_to_right))
            if left_to_right:
                for n in nodes_on_same_level:
                    result[level].append(n.val)
            else:
                for i in range(len(nodes_on_same_level) - 1, -1, -1):
                    n = nodes_on_same_level[i]
                    result[level].append(n.val)
        return result

题目依旧是要求按层遍历,所以广度优先搜索仍然是我选用的方法。于是读者可以发现本题解法和上一题有着很类似的结构,也有人称之为“套公式”。不同的第一处在于队列中的 tuple 现在有三个元素了,增加了末尾的一个布尔类型。该布尔变量值为 True 的时候说明当前层级需要从左往右遍历,反之从右往左。在循环中,程序一次性将一层的节点全部找出,按照布尔变量指定的顺序添加到结果数组中。剩下的工作就是将子节点进队,和上一题同样的是层级要加 1,不同的是要将布尔变量扭转后再存入队列。

总结

这篇较短的博文首先概括了广度优先搜索的思想,然后通过两道难度有递进的题目进一步介绍了这种算法,希望大家在刷题道路上有更多收获;对我的文章、代码有指点的也欢迎评论,我会即时回复。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值