递归执行广度优先搜索
假设您想要递归地实现二叉树的广度优先搜索。你会怎样做呢?
是否可以仅使用调用堆栈作为辅助存储?
Let's say you wanted to implement a breadth-first search of a binary tree recursively. How would you go about it?
Is it possible using only the call-stack as auxiliary storage?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(24)
(我假设这只是某种思维练习,甚至是一个技巧作业/面试问题,但我想我可以想象一些奇怪的场景,由于某种原因你不允许任何堆空间[一些非常糟糕的习惯]内存管理器?一些奇怪的运行时/操作系统问题?]而您仍然可以访问堆栈...)
广度优先遍历传统上使用队列,而不是堆栈。队列和堆栈的性质几乎相反,因此尝试使用调用堆栈(这是一个堆栈,因此得名)作为辅助存储(队列)几乎注定会失败,除非您正在这样做调用堆栈中的一些愚蠢可笑的事情是你不应该做的。
同样,您尝试实现的任何非尾递归的本质本质上都是向算法添加堆栈。这使得它不再在二叉树上进行广度优先搜索,因此传统 BFS 的运行时等不再完全适用。当然,您总是可以轻松地将任何循环转换为递归调用,但这不是任何有意义的递归。
然而,正如其他人所演示的,有一些方法可以以一定的成本实现遵循 BFS 语义的东西。如果比较的成本很昂贵但节点遍历很便宜,则如 @Simon Buchan 做到了,您可以简单地运行迭代深度优先搜索,仅处理叶子。这意味着堆中没有存储增长的队列,只是一个本地深度变量,并且随着树被一遍又一遍地遍历,堆栈在调用堆栈上一遍又一遍地构建。正如 @Patrick 指出的,由数组支持的二叉树是无论如何,通常以广度优先遍历顺序存储,因此对其进行广度优先搜索将是微不足道的,也不需要辅助队列。
(I'm assuming that this is just some kind of thought exercise, or even a trick homework/interview question, but I suppose I could imagine some bizarre scenario where you're not allowed any heap space for some reason [some really bad custom memory manager? some bizarre runtime/OS issues?] while you still have access to the stack...)
Breadth-first traversal traditionally uses a queue, not a stack. The nature of a queue and a stack are pretty much opposite, so trying to use the call stack (which is a stack, hence the name) as the auxiliary storage (a queue) is pretty much doomed to failure, unless you're doing something stupidly ridiculous with the call stack that you shouldn't be.
On the same token, the nature of any non-tail recursion you try to implement is essentially adding a stack to the algorithm. This makes it no longer breadth first search on a binary tree, and thus the run-time and whatnot for traditional BFS no longer completely apply. Of course, you can always trivially turn any loop into a recursive call, but that's not any sort of meaningful recursion.
However, there are ways, as demonstrated by others, to implement something that follows the semantics of BFS at some cost. If the cost of comparison is expensive but node traversal is cheap, then as @Simon Buchan did, you can simply run an iterative depth-first search, only processing the leaves. This would mean no growing queue stored in the heap, just a local depth variable, and stacks being built up over and over on the call stack as the tree is traversed over and over again. And as @Patrick noted, a binary tree backed by an array is typically stored in breadth-first traversal order anyway, so a breadth-first search on that would be trivial, also without needing an auxiliary queue.
如果使用数组来支持二叉树,则可以通过代数方式确定下一个节点。如果
i
是一个节点,那么它的子节点可以在2i + 1
(对于左节点)和2i + 2
(对于左节点)找到右节点)。节点的下一个邻居由i + 1
给出,除非i
是2
的幂这是广度优先搜索的非常简单的实现的伪代码在数组支持的二叉搜索树上。这假设一个固定大小的数组,因此是一个固定深度的树。它将查看无父节点,并可能创建一个难以管理的大堆栈。
If you use an array to back the binary tree, you can determine the next node algebraically. if
i
is a node, then its children can be found at2i + 1
(for the left node) and2i + 2
(for the right node). A node's next neighbor is given byi + 1
, unlessi
is a power of2
Here's pseudocode for a very naive implementation of breadth first search on an array backed binary search tree. This assumes a fixed size array and therefore a fixed depth tree. It will look at parentless nodes, and could create an unmanageably large stack.
我找不到完全递归的方法(没有任何辅助数据结构)。但是如果队列 Q 是通过引用传递的,那么您可以拥有以下傻尾递归函数:
I couldn't find a way to do it completely recursive (without any auxiliary data-structure). But if the queue Q is passed by reference, then you can have the following silly tail recursive function:
以下方法使用 DFS 算法来获取特定深度的所有节点 - 这与对该级别执行 BFS 相同。如果你找出树的深度并对所有级别执行此操作,结果将与 BFS 相同。
找到一棵树的深度是小菜一碟:
The following method used a DFS algorithm to get all nodes in a particular depth - which is same as doing BFS for that level. If you find out depth of the tree and do this for all levels, the results will be same as a BFS.
Finding depth of a tree is a piece of cake:
Java 中的简单 BFS 和 DFS 递归:
只需将树的根节点推送/提供到堆栈/队列中并调用这些函数即可。
A simple BFS and DFS recursion in Java:
Just push/offer the root node of the tree in the stack/queue and call these functions.
这是一个 BFS 递归遍历 Python 实现,适用于没有循环的图。
Here is a BFS recursive traversal Python implementation, working for a graph with no cycle.
我想将我的美分添加到最佳答案中,因为如果语言支持诸如生成器之类的东西,则可以通过以下方式完成bfs: -递归地。
首先,@Tanzelax 的回答是:
事实上,普通函数调用的堆栈不会表现得像一个普通的堆栈。但是生成器函数将暂停函数的执行,因此它使我们有机会生成下一级节点的子节点,而无需深入研究该节点的更深层次的后代。
以下代码是 Python 中的递归 bfs。
这里的直觉是:
I would like to add my cents to the top answer in that if the language supports something like generator, bfs can be done co-recursively.
To begin with, @Tanzelax's answer reads:
Indeed, ordinary function call's stack won't behave like a normal stack. But generator function will suspend the execution of function so it gives us the chance to yield next level of nodes' children without delving into deeper descendants of the node.
The following code is recursive bfs in Python.
The intuition here is:
我发现了一个非常漂亮的递归(甚至是函数式)广度优先遍历相关算法。不是我的想法,但我认为应该在本主题中提及。
Chris Okasaki 在 ICFP 2000 中解释了他的广度优先编号算法,网址为 http://okasaki.blogspot.de/2008/07/breadth-first-numbering-algorithm-in.html非常清楚,只有3张图片。
Debasish Ghosh 的 Scala 实现,我在 http:// debasishg.blogspot.de/2008/09/breadth-first-numbering-okasakis.html 是:
I found a very beautiful recursive (even functional) Breadth-First traversal related algorithm. Not my idea, but i think it should be mentioned in this topic.
Chris Okasaki explains his breadth-first numbering algorithm from ICFP 2000 at http://okasaki.blogspot.de/2008/07/breadth-first-numbering-algorithm-in.html very clearly with only 3 pictures.
The Scala implementation of Debasish Ghosh, which i found at http://debasishg.blogspot.de/2008/09/breadth-first-numbering-okasakis.html, is:
笨办法:
The dumb way:
这是简短的 Scala 解决方案:
使用返回值作为累加器的想法非常适合。
可以用类似的方式用其他语言实现,只需确保您的递归函数处理节点列表。
测试代码列表(使用@marco测试树):
输出:
Here is short Scala solution:
Idea of using return value as accumulator is well suited.
Can be implemented in other languages in similar way, just make sure that your recursive function process list of nodes.
Test code listing (using @marco test tree):
Output:
这是一个 python 实现:
Here's a python implementation:
这是递归 BFS 的 Scala 2.11.4 实现。为了简洁起见,我牺牲了尾部调用优化,但 TCOd 版本非常相似。另请参阅 @snv 的帖子。
Here's a Scala 2.11.4 implementation of recursive BFS. I've sacrificed tail-call optimization for brevity, but the TCOd version is very similar. See also @snv's post.
使用 Haskell,以下对我来说似乎很自然。在树的各个级别上递归迭代(这里我将名称收集到一个大的有序字符串中以显示通过树的路径):
The following seems pretty natural to me, using Haskell. Iterate recursively over levels of the tree (here I collect names into a big ordered string to show the path through the tree):
我必须实现以 BFS 顺序输出的堆遍历。它实际上不是 BFS,但完成相同的任务。
I had to implement a heap traversal which outputs in a BFS order. It isn't actually BFS but accomplishes the same task.
设 v 为起始顶点
设 G 为所讨论的图
以下是不使用队列的伪代码
Let v be the starting vertex
Let G be the graph in question
The following is the pseudo code without using queue
二叉(或 n 叉)树的 BFS 可以在没有队列的情况下递归完成,如下所示(此处为 Java):
按升序遍历打印数字 1-12 的示例遍历:
BFS for a binary (or n-ary) tree can be done recursively without queues as follows (here in Java):
An example traversal printing numbers 1-12 in ascending order:
来自在 AlgoExpert 上学习时对这个问题的改编。提示中已提供以下
Class
。以下是 python 中的迭代和递归解决方案。此问题的目标是返回一个输出数组,其中按访问顺序列出了节点的名称。所以如果遍历的顺序是A->; B-> D-> F 输出为 ['A','B','D','F']递归
迭代
From an adaptation of this question while studying on AlgoExpert. The following
Class
is provided already in the prompt. Here are iterative and recursive solutions in python. The goal of this problem is to return an outputarray
which lists the name of the nodes in order visited. So if the order of traversal was A -> B -> D -> F the output is ['A','B','D','F']Recursive
Iterative
除了最佳答案中的精彩解释之外,我还会添加另一个可能的解决方案没有队列 。它使用递归和生成器:
注意:出于演示目的,无论树深度如何,它始终进行 10 次递归调用。对于更高级的版本,请参见下文。
测试脚本:
输出:
更高级的版本涉及根据访问的级别停止递归。例如:
输出:
In addition to the excellent explanation in the top answer, I would add another possible solution without queues. It uses recursion and generators:
NOTE: For demonstration purposes, it always makes 10 recursive calls regardless of the tree depth. For a more advanced version, see below.
Test script:
Output:
A more advanced version involves stopping recursion depending on the levels visited. For example:
Output:
这是一个 JavaScript 实现,它用深度优先递归来伪造广度优先遍历。我将每个深度的节点值存储在哈希内部的数组内。如果某个级别已经存在(我们发生了冲突),那么我们只需将其推送到该级别的数组即可。您也可以使用数组而不是 JavaScript 对象,因为我们的级别是数字并且可以用作数组索引。您可以返回节点、值、转换为链接列表或任何您想要的内容。为了简单起见,我只是返回值。
这是使用迭代方法的实际广度优先遍历的示例。
Here is a JavaScript Implementation that fakes Breadth First Traversal with Depth First recursion. I'm storing the node values at each depth inside an array, inside of a hash. If a level already exists(we have a collision), so we just push to the array at that level. You could use an array instead of a JavaScript object as well since our levels are numeric and can serve as array indices. You can return nodes, values, convert to a Linked List, or whatever you want. I'm just returning values for the sake of simplicity.
Here is an example of actual Breadth First Traversal using an iterative approach.
以下是我在不使用循环和队列的情况下完全递归实现双向图广度优先搜索的代码。
Following is my code for completely recursive implementation of breadth-first-search of a bidirectional graph without using loop and queue.
二叉树递归广度优先搜索算法的 C# 实现。
二叉树数据可视化
如果您希望算法不仅适用于二叉树,还适用于图可以有两个或更多指向同一个另一个节点的节点,您必须通过保存已访问节点的列表来避免自循环。实施可能看起来像这样。
图数据可视化
C# implementation of recursive breadth-first search algorithm for a binary tree.
Binary tree data visualization
If you want algorithm to work not only with binary-tree but with graphs what can have two and more nodes that points to same another node you must to avoid self-cycling by holding list of already visited nodes. Implementation may be looks like this.
Graph data visualization
我用 C++ 编写了一个程序,它也可以在联合图和不相交图中工作。
I have made a program using c++ which is working in joint and disjoint graph too .
我认为这可以使用指针来完成,不使用任何队列。
基本上我们在任何一点都维护两个指针,一个指向父级,另一个指向要处理的子级(链表指向所有已处理的子级)
现在您只需分配子级的指针即可当父级处理完成时,您只需使子级成为父级以处理下一级,
以下是我的代码:
//Algorightm:
//驱动程序代码:
I think this can be done using pointers, without using any QUEUE.
Basically we are maintaining two pointers at any point, one is pointing to the parents, the other is pointing to the children to be processed ( linkedlist to all which have been processed )
Now you simply assign the pointer of the child & when parent processing finishes you just make the child to be parent for processing next level
following is my code :
//Algorightm :
//Driver code :