(通俗易懂)《面试》-- 广度优先搜索(BFS)与深度优先搜索(DFS)Python实现

前言

这两种都是作为经典的图搜索算法,目的都是系统地展开并检查图中所有的节点,但是这两种算法有着不同的搜索方式,以搜索二叉树为例(二叉树其实也是一种特殊的图结构),广度优先搜索(BFS)从上往下搜索只有等待上一层所有节点都遍历完毕才会转到下一层,而深度优先搜索(DFS)则是开始就一直往深处走,知道找到解或者走到底部为止。

广度优先搜索(BFS)

BFS思想(以二叉树为例子)

1、从根节点开始;

2、将节点保存到队列(queue)

3、取出队列中首位节点,读取该节点的值和判断是否有孩子节点,若有则将孩子节点依次添加到队列尾部

4、重复3步骤,直到队列为空

# 定义树节点的类
class TreeNode:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None


def BFS(node):
    # 广度优先算法使用队列
    if node is None:
        return None

    queue = [node]
    result = []

    while len(queue):
        cnt = queue.pop(0)        # 取出队列中首位节点
        result.append(cnt.val)    # 读取该节点的值

        if cnt.left:              # 判断是否有左子节点,若有则添加到队列中
            queue.append(cnt.left)

        if cnt.right:             # 判断是否有右子节点,若有则添加到队列中
            queue.append(cnt.right)
    return result


if __name__ == '__main__':
    a = TreeNode(0)
    b = TreeNode(1)
    c = TreeNode(2)
    d = TreeNode(3)
    e = TreeNode(4)
    f = TreeNode(5)
    g = TreeNode(6)
    a.left = b
    a.right = c
    b.left = d
    b.right = e
    c.left = f
    c.right = g

    print(BFS(a))

深度优先搜索(DFS)

DFS思想(二叉树为例):

1、将根节点放入栈(stack)数据结构中

2、从栈中取出第一个节点,判断是否有孩子节点,若有则将该节点某一个尚未搜索过的孩子节点放入stack中

3、重复步骤2

def DFS(node):
    # 深度优先搜索使用栈容器
    if not node:
        return None

    stack = [node]          # 栈结构
    nodeSet = set()         # set容器,保证节点只被遍历一次
    nodeSet.add(node)       # 根节点已被遍历,所以添加到set中
    result = [node.val]
    while len(stack):
        cur = stack.pop()
        for nextNode in (cur.left, cur.right):      # 如果是二叉树结构,需要按照从左往右顺序
            if nextNode is not None and nextNode not in nodeSet:    # 保证孩子节点尚未搜索过
                # 这里放回去的原因是当搜索孩子节点的时候,父节点需要在栈内
                stack.append(cur)
                stack.append(nextNode)              # 添加父节点后再添加孩子节点
                nodeSet.add(nextNode)               # 遍历过的节点使用set容器保存起来
                result.append(nextNode.val)
                break                               # 退出,保持深度优先
    return result

疑惑:

1、为什么弹出了cur以后还要把cur压入栈内呢?

答:为了保证出栈顺序是按照深度优先搜索,如果cur没有了子节点,则直接弹出栈。如果有子节点,那么先将cur压栈再将cur子节点压栈可以保证栈顶的节点一定有父节点(方便遍历父节点的另外子节点)。

2、为什么需要加上break语句?

保持深度优先,假设某个节点(node)既有左子节点(left)又有右子节点(right),同时左子节点还有子节点(leftch)

如果没有break的情况压栈顺序是 node -- left -- right -- leftch,同样遍历顺序也是 node -- left -- right -- leftch,明显不符合DFS

如果有break的情况栈的变化为:

1、node -- left -- leftch(leftch没有子节点,下一步开始弹栈)

2、node -- left (left 的子节点已经被遍历过,继续弹栈)

3、node (虽然node的子节点left已经被遍历过,但是右子节点并未遍历,因此下一步将right压栈)

4、node -- right

明显符合深度优先的规律。

 

 

你可能感兴趣的:(面试)