我有一个递归算法,我用它来迭代分层数据结构,但不幸的是,对于一些数据,分层结构太深,以至于我得到了一个StackOverflow错误。我见过这种情况发生在大约150个节点的深度上,而数据可能会增长到更远的程度。对于上下文,这段代码将在有限的环境中运行,改变JVM堆栈大小不是一个选项,数据结构是给定的,代表不同的文件系统和目录和文件。
为了解决堆栈溢出问题,我尝试将算法转换为迭代算法。这不是我以前必须做的事情,所以我从一些例子开始,展示如何用简单的递归来做这件事,但是我不确定如何将其应用于循环内的递归。我找到了一种似乎有效的方法,但是代码相当疯狂。
以下是我最初递归方法的简化版本:
private CacheEntry sumUpAndCacheChildren(Node node) {
final CacheEntry entry = getCacheEntry(node);
if (entryIsValid(entry))
return entry;
Node[] children = node.listChildren();
long size = 0;
if (children != null) {
for (Node child : children) {
if (child.hasChildren()) {
size += sumUpAndCacheChildren(child).size;
} else {
size += child.size();
}
}
}
return putInCache(node, size);
}
每个叶节点都有一个大小,而任何祖先节点的大小都被视为其所有子节点的大小。我想知道每个节点的大小,所以每个节点的大小都是聚合和缓存的。
以下是迭代版本:
private CacheEntry sumUpAndCacheChildren(Node initialNode) {
class StackFrame {
final Node node;
Node[] children;
// Local vars
long size;
// Tracking stack frame state
int stage;
int loopIndex;
StackFrame(Node node) {
this.node = node;
this.children = null;
this.size = 0;
this.stage = 0;
this.loopIndex = 0;
}
}
final Stack<StackFrame> stack = new Stack<StackFrame>();
stack.push(new StackFrame(initialNode));
CacheEntry retValue = getCacheEntry(initialNode);
outer:
while (!stack.isEmpty()) {
final StackFrame frame = stack.peek();
final Node node = frame.node;
switch(frame.stage) {
case 0: {
final CacheEntry entry = getCacheEntry(node);
if (entryIsValid(entry)) {
retValue = entry;
stack.pop();
continue;
}
frame.children = node.asItem().listChildren();
frame.stage = frame.children != null ? 1 : 3;
} break;
case 1: {
for (int i = frame.loopIndex; i < frame.children.length; ++i) {
frame.loopIndex = i;
final Node child = frame.children[i];
if (child.hasChildren()) {
stack.push(new StackFrame(child));
frame.stage = 2; // Accumulate results once all the child stacks have been calculated.
frame.loopIndex++; // Make sure we restart the for loop at the next iteration the next time around.
continue outer;
} else {
frame.size += child.size();
}
}
frame.stage = 3;
} break;
case 2: {
// Accumulate results
frame.size += retValue.size;
frame.stage = 1; // Continue the for loop
} break;
case 3: {
retValue = putInCache(node, frame.type);
stack.pop();
continue;
}
}
}
return retValue;
}
这感觉比它需要的更疯狂,在代码中所有的地方都要这样做会很痛苦,在那里我递归到孩子们身上,并对他们做不同的操作。当我在每个级别聚合并在孩子们的for循环中这样做时,我可以使用什么技术来使递归更容易?
编辑:
在下面的答案的帮助下,我能够极大地简化事情。该代码现在几乎与原始递归版本一样简洁。现在,我只需要在其他地方应用相同的原理,在相同的数据结构上递归。
好吧,我要用人类的语言来解释,因为我现在不想编码:
你只需要在循环头放一个布尔值,如果孩子列表中没有元素了,就把它设置为false...我希望我能正确表达自己,随时提问和/或询问澄清。
这个算法将以指数级的速度变慢(--
基本上就是对一棵N元树进行后序迭代遍历;你可以试着搜索更详细的例子。
在非常粗糙的伪代码中:
Node currentNode;
Stack<Node> pathToCurrent;
Stack<Integer> sizesInStack;
Stack<Integer> indexInNode;
pathToCurrent.push(rootNode);
sizesInStack.push(0);
indexInNode.push(0);
current = rootNode;
currentSize = 0;
currentIndex = 0;
while (current != null) {
if (current.children != null && currentIndex < current.children.size) {
//process the next node
nextChild = current.children[currentIndex];
pathToCurrent.push(current);
sizesInStack.push(currentSize);
indexInNode.push(currentIndex);
current = nextChild;
currentSize = 0;
currentIndex = 0;
} else {
//this node is a leaf, or we've handled all its children
//put our size into the cache, then pop off the stack and set up for the next child of our parent
currentSize += this.size();
putInCache(this, currentSize);
current = pathToCurrent.pop(); //If pop throws an exception on empty stack, handle it here and exit the loop
currentSize = currentSize + sizesInStack.pop();
currentIndex = 1 + indexInNode.pop();
}
}
由于您处理的是树结构,并且希望计算累积大小,因此在跟踪每个节点的父节点时,请尝试使用DFS。我在这里假设您不能更改或子类化节点
,我保留了您使用的所有函数签名。
private class SizedNode {
public long cumulativeSize;
public Node node;
public SizedNode parent;
public SizedNode(SizedNode parent, Node node) {
this.node = node;
this.parent = parent;
}
public long getSize() {
if (node.hasChildren()) {
return cumulativeSize;
}
else {
return node.size();
}
}
}
private void sumUpAndCacheChildren(Node start)
{
Stack<SizedNode> nodeStack = new Stack<SizedNode>();
// Let's start with the beginning node.
nodeStack.push(new SizedNode(null, start));
// Loop as long as we've got nodes to process
while (!nodeStack.isEmpty()) {
// Take a look at the top node
SizedNode sizedNode = nodeStack.peek();
CacheEntry entry = getCacheEntry(sizedNode.node);
if (entryIsValid(entry)) {
// It's cached already, so we have computed its size
nodeStack.pop();
// Add the size to the parent, if applicable.
if (sizedNode.parent != null) {
sizedNode.parent.cumulativeSize += sizedNode.getSize();
// If the parent's now the top guy, we're done with it so let's cache it
if (sizedNode.parent == nodeStack.peek()) {
putInCache(sizedNode.parent.node, sizedNode.parent.getSize());
}
}
}
else {
// Not cached.
if (sizedNode.node.hasChildren()) {
// It's got a bunch of children.
// We can't compute the size yet, so just add the kids to the stack.
Node[] children = sizedNode.node.listChildren();
if (children != null) {
for (Node child : children) {
nodeStack.push(new SizedNode(sizedNode, child));
}
}
}
else {
// It's a leaf node. Let's cache it.
putInCache(sizedNode.node, sizedNode.node.size());
}
}
}
}
我有两个非递归方法,其中一个读取字符串中的总“e”字符,另一个检查 ArrayList 是否按字母顺序排列。 递归方法的定义是方法调用自身。我相信我理解这个概念,但要实现它或将其转换为递归方法确实很困难。我怎样才能将这些方法转化为递归方法,同时我应该如何思考?此外,这是我的另一种方法,它只打印出指定数字大小的数字。 条件方法检查数字的第一个数字(从右起)是否大于第二个数字,并再次检查第二个是否大于
问题内容: 是否每个递归函数都有一个等效的for循环?(两者都达到相同的结果)。 我有这个递归函数: 假设单词是Set [],并且单词[i] =单词长度为i的集合。 我想做的是:使用一个单词(例如,“ stackoverflow”,没有空格)启动递归,我试图查找该单词是否可以切成子单词(“ stack”,“ over”,“ flow”) ..子词的最小长度为3,并且假设长度为i的子词在Set wo
我有一个关于如何将“递归”转换为“尾部递归”的问题。 这不是家庭作业,只是在我试图润色算法书籍中的递归定理时出现的一个问题。 我熟悉使用递归的两个典型示例(阶乘和斐波那契序列),也知道如何以递归方式和尾部递归方式实现它们。 我的代码如下(我使用Perl只是为了使其简单,但可以轻松地转换为C/Java/C)。 运行代码时,输出如下: 递归函数在返回之前使用不同的参数调用自己两次。我尝试了几种方法将其
我在scheme中构建了一个递归函数,它将在一些输入上重复给定的函数f,n次。 我需要用尾递归构建这个函数的迭代版本,如果我正确理解尾递归,我认为我做得对。 我的问题是,这真的是迭代的吗?我相信我已经使用尾部递归正确地构建了它,但从技术上讲,它仍然将一系列操作推迟到count=0,在这里,它执行叠加的任意多个组合。
我需要帮助将这个while循环方法转换为递归方法 提前致谢
我有一个对象,里面有多个嵌套对象。类: 其基础是,一个可以有多个以及它们自己的。我想为每个填充,如下所示: 这是起作用的,但我想用一个递归的方法来做这件事。这可能吗? 根据@johnathan Barclay的建议编辑我使用以下方法: 这有助于获取每个第一个的。现在发生的情况是以下子元素没有填充。