关于BFS算法【结点组织方式】的一点思考 2020-08-14(

2020-08-14  本文已影响0人  9_SooHyun

1.最常见的BFS算法结构

class Solution:
    def bfs(self, root):
        if not root:
            return  
            
        queue = list()
        queue.append(root)
        # 核心代码开始
        while queue:
            l = len(queue)
            for i in range(l):
                out = queue.pop(0)
                for child in out.children:
                    queue.append(child)

首先要明确,BFS的核心是【滚动组织最新一层的节点】

上面是我们最熟悉的BFS结构,通过维护一个队列,不断弹出结点并把下一层结点加入队列来实现整个BFS,每一层结点的组织方式在这里是【队列】

也许这种结构太习以为常了,于是算法思维就没能再打破一些条条框框,总是想着BFS就是搞个队列然后实现

今天刚好碰到两个BFS的问题,对【结点组织方式】有了一点思考

2.先说思考的结论

BFS中,结点是分层的,对每一层结点我们需要用一种数据结构把它们组织起来,最常见的就是上面的队列了。而队列的本质是什么,线性表。用线性表去组织每一层结点,非常自然。但是,线性表除了队列这样的顺序表,还有链表,链表同样可以拿来组织每层的结点。再进一步想,线性表可以组织每层结点,非线性的数据结构可以吗?当然是可以的,集合、哈希表都可以用来组织每一层的结点

线性结构和非线性结构组织每层结点的区别主要在于,线性结构可以天然保存结点的先后顺序,而当我们并不关心同层结点间的顺序而恰好有一些查找结点的需求,那么集合、哈希表这样的结构就派上用场了

每层结点组织方式:

3.实战感受

3.1 链表组织每层结点

117. 填充每个节点的下一个右侧节点指针 II

给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
初始状态下,所有 next 指针都被设置为 NULL
你只能使用常量级额外空间

分析:
这题用常规的层次遍历很好做,用队列组织每一层结点,然后按顺序调整next指针。但是要求空间复杂度O(1),这就要想办法改造层次遍历了。根据题目要求,每层结点通过next指针相连形成链表,那我们就可以天然借助链表这个结构去访问和组织每一层的结点。核心就是,队列组织每层结点 -> 链表组织每层结点,但本质也还是一样的

"""
# Definition for a Node.
class Node:
    def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next
"""

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        cur = root
        while cur: # 从每层头节点cur开始遍历当前层,并链接下一层
            pointer = Node() # 用pointer走一遍来链接下一层
            most_left = pointer
            # 疯狂链接下一层
            while cur:
                if cur.left:
                    pointer.next = cur.left
                    pointer = pointer.next
                if cur.right:
                    pointer.next = cur.right
                    pointer = pointer.next
                cur = cur.next
            
            # cur指向链接好的下一层的最左节点
            cur = most_left.next
        return root

3.2 集合组织每层结点

127. 单词接龙

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。

说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5

分析:
每个单词看成一个节点,相差只有一个字母的单词都认为邻接,就抽象成一个无向图结构的问题。问题变成,无向图中寻找到从起点到终点的最短路径距离,后面就是BFS

因此,问题的关键实际上在于建图

如{'*it':['hit'], 'h*t':['hit','hot'], ...}
class Solution(object):
    ###### 基于集合的双向BFS模式 ######
    def ladderLength(self, beginWord, endWord, wordList):
        if endWord not in wordList:
            return 0

        # 基于wordList建图,使用【同类表】形式。如{'*it':['hit'], 'h*t':['hit','hot'], ...}
        # 建立了图之后就是常规的bfs问题
        size, graph_dic = len(beginWord), defaultdict(list)
        for w in wordList:
            for _ in range(size):
                graph_dic[w[:_]+"*"+w[_+1:]].append(w)
        
        # 两个set的双向BFS
        s, s1 = set(), set()
        s.add(beginWord)
        s1.add(endWord)
        # 标记哪些单词已经访问
        mark_dic = defaultdict(bool)  # bool 的默认值是false,因此所有不在list里的是false
        mark_dic[beginWord] = True
        mark_dic[endWord] = True
        steps = 1
        while s and s1:
            # strick: 总是选择元素少的一方进行bfs扩散
            if len(s) > len(s1):
                s, s1 = s1, s
            
            # 用temp记录本轮s集合的bfs扩散结果
            temp = set()
            l = len(s)
            for _ in range(l):
                cur_word = s.pop() # 随机pop一个
                for i in range(size):               # 找邻居
                    for neighbour in graph_dic[cur_word[:i]+"*"+cur_word[i+1:]]:
                        # 如果从起点、终点扩散的两军前锋相遇,判断元素是否在集合中比判断是否在数组中更快
                        if neighbour in s1: 
                            return steps + 1
                        if not mark_dic[neighbour]:
                            mark_dic[neighbour] = True
                            temp.add(neighbour)  
            steps += 1
            s = s1
            s1 = temp
        return 0
上一篇 下一篇

猜你喜欢

热点阅读