【问题标题】:Python: Convert a depth first search to a breadth first search for all combinations of a listPython:将深度优先搜索转换为广度优先搜索列表的所有组合
【发布时间】:2018-01-19 03:59:33
【问题描述】:

我有一个简单的递归函数,它提供对选项列表的每个可能组合的深度优先搜索:

def twoCharacters_dfs(options, used):
    for i in range(len(options)):
        used.append(options.pop(0))
        print("used=", used)
        twoCharacters_dfs(options, used)
    if len(used) == 0:
        return
    options.append(used.pop())

twoCharacters_dfs(['q', 'w', 'e', 'r'], [])

输出(由于长度而缩短)如下所示:

used= ['q']
used= ['q', 'w']
used= ['q', 'w', 'e']
used= ['q', 'w', 'e', 'r']
used= ['q', 'w', 'r']
used= ['q', 'w', 'r', 'e']
used= ['q', 'e']
used= ['q', 'e', 'r']
used= ['q', 'e', 'r', 'w']
used= ['q', 'e', 'w']
used= ['q', 'e', 'w', 'r']
....
used= ['w']
....
used= ['e']
....
used= ['r']
....

这一切都很好,可以按我的意愿工作。但我有兴趣将其从深度优先转换为广度优先,因此输出看起来更像:

used= ['q']
used= ['w']
used= ['e']
used= ['r']
used= ['q', 'w']
used= ['q', 'e']
used= ['q', 'r']
used= ['w', 'q']
used= ['w', 'e']
used= ['w', 'r']
....

我已经能够(只有一个硬编码的固定长度列表)迭代地完成它,但需要一个递归解决方案,以便它可以适用于任何长度的选项。我也故意避免提供我所寻求的功能的 python 库,因为我想了解事物是如何工作的,并构建自己的东西作为学习练习。

我觉得有一个简单的解决方案,但我无法将广度优先算法概念化到我的代码中。

更新

在尝试递归 BFS 解决方案之前,我想创建一个迭代 BFS 解决方案,因为它似乎更容易实现。事实证明,我也很难做到这一点。

def twoCharacters_bfs_iterative(options, used):
    for option in options:
        print("using option = ", option)

    for option1 in options:
        list2 = options[:]
        list2.remove(option1)
        for option2 in list2:
            print("using option = ", option1, option2)

    for option1 in options:
        list2 = options[:]
        list2.remove(option1)
        for option2 in list2:
            list3 = list2[:]
            list3.remove(option2)
            for option3 in list3:
                print("using option = ", option1, option2, option3)

这实现了我想要的输出(如上所列),但仅适用于我知道长度的集合。我想将其扩展为任意长度的列表,但在这样做时遇到了麻烦。我想如果我能让迭代解决方案发挥作用,递归解决方案也不会落后。

【问题讨论】:

  • 我不知道 BFS 作为递归函数是否有意义。 DFS 和 BFS 的基本区别在于 DFS 使用堆栈来跟踪未访问的节点,而 BFS 使用队列。递归 DFS 实现基本上只是将调用堆栈用作该堆栈。尝试编写一个非递归 DFS(提示:python 列表可以被视为堆栈),然后尝试用队列替换该实现中的堆栈(您可以使用 queue.Queue 作为 FIFO 队列)
  • @PatrickHaugh 这是一个很好的提示。想锻炼我的递归肌肉,但是转到非递归 DFS 然后从那里转到非递归 BFS 对我来说是一个很好的学习过程
  • 100% 同意@PatrickHaugh,将 BFS 实现为递归函数需要跳过一些复杂的环节。 BFS 使用循环更自然地实现。
  • 关于 DFS “使用调用堆栈”作为数据结构本身的见解很棒。
  • @Turksarama:这不是很复杂的篮球;有些语言中递归是唯一可用的循环结构,它们显然仍然可以实现 BFS。问题更多的是 Python 不 (and never will) 支持尾调用优化,所以你不能在不浪费大量内存的情况下做到这一点。

标签: python combinations depth-first-search breadth-first-search


【解决方案1】:

编辑:我没有从示例中注意到所有排列都是必需的。遵循一个使用列表作为队列的函数:

def bfs(options):
    queue = [([c], [*options[:i], *options[i+1:]]) for i,c in enumerate(options)]
    while len(queue) > 0:
        head, tail = queue[0]
        print(head)
        queue.extend([([*head, c], [*tail[:i], *tail[i+1:]]) for i,c in enumerate(tail)])
        del queue[0]

像这样工作(64 行,截断):

>>> bfs(['q','w','e','r'])
['q']
['w']
['e']
['r']
['q', 'w']
['q', 'e']
...
['r', 'w']
['r', 'e']
['q', 'w', 'e']
['q', 'w', 'r']
['q', 'e', 'w']
...
['r', 'q', 'e', 'w']
['r', 'w', 'q', 'e']
['r', 'w', 'e', 'q']
['r', 'e', 'q', 'w']
['r', 'e', 'w', 'q']

还有,

def bfs(options):
    queue = [([c], [*options[:i], *options[i+1:]]) for i,c in enumerate(options)]
    for head, tail in queue:
        queue.extend([([*head, c], [*tail[:i], *tail[i+1:]]) for i,c in enumerate(tail)])
    return [head for head, tail in queue]

这个版本返回一个列表而不是打印。


遵循上一个答案,不考虑排列


正如 cmets 中的其他人已经说过的那样,这不自然。遵循“递归”函数:

def bfs(options, level=0):
    if level == 0:
        for c in options:
            print([c])
        for i in range(1,len(options)):
            bfs(options, i)
    else:
        for i,c in enumerate(options):
            for j,g in enumerate(options[i+1:]):
                if i+1+j+level <= len(options):
                    print([c,*options[i+1+j:i+1+j+level]])

最后一行中的 * 需要 Python3,但您可以将其删除。

预期的输出是:

['q']
['w']
['e']
['r']
['q', 'w']
['q', 'e']
['q', 'r']
['w', 'e']
['w', 'r']
['e', 'r']
['q', 'w', 'e']
['q', 'e', 'r']
['w', 'e', 'r']
['q', 'w', 'e', 'r']

另一个版本:

def bfs(options, level=0):
    for i,c in enumerate(options):
        for j,g in enumerate(options[i+1:]):
            if i+1+j+level <= len(options):
                print([c,*options[i+1+j:i+1+j+level]])
            if level == 0:
                break
    if level < len(options):
        bfs(options, level + 1)

【讨论】:

  • 太棒了。我有使用关卡的相同概念,但在尝试实现类似于 OP 的 DFS 的单独 dict/list 时陷入困境。现在我可以睡觉了……谢谢。
  • 这是解决方案的一个良好开端,但有一点:顺序是相关的,所以我需要所有可能的组合。例如:[q,w] 和 [w,q] 应该出现在列表中。我认为我缺少的关键是跟踪级别。我今天将尝试以此为基础...
  • 看看编辑:这是您要找的吗?
  • @MatteoT。是的,这更多的是我正在寻找的。它不是递归的,但是在阅读了有关 DFS 和 BFS 的更多信息后,我很满意使用迭代解决方案作为递归 BFS 并没有多大意义。我最终修改了您的解决方案以获得更多我的编码风格,但您的工作并且将被标记为这样。谢谢!
【解决方案2】:

我正在发布我自己的问题的答案,以提供有关深度优先搜索和广度优先搜索的一些说明。我最初的目标是递归深度优先函数的递归广度优先版本。这是因为对 DFS 和 BFS 之间的根本区别缺乏了解:DFS 使用堆栈,而 BFS 使用队列。 (感谢@Patrick Haugh 的洞察力以及这篇文章:Performing Breadth First Search recursively)。

DFS 使用堆栈这一事实非常适合递归函数,因为您可以将调用堆栈用作操作堆栈。但这不适用于 BFS 的队列样式。广度优先搜索可以递归地完成,但最终有点类似于深度优先搜索。将 BF 保留为迭代函数更加简洁直观。

在不了解代码为什么会起作用的情况下,从不喜欢复制/粘贴代码,@Matteo T. 正确答案引导我使用我目前正在实施的没有枚举的迭代 BFS 解决方案:

def bfs_iterative(options):
    queue = [[item] for item in options]
    while queue:
        using = queue.pop(0)
        print(using)
        remaining = [item for item in options if item not in using]
        extension = []
        for item in remaining:
            using.append(item)
            extension.append(using[:])
            using.pop()
        queue.extend(extension)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-31
    • 1970-01-01
    • 2016-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多