当前位置: 首页 > 知识库问答 >
问题:

将递归方法(递归在循环内完成)转换为迭代方法

郜玉石
2023-03-14

我有一个递归算法,我用它来迭代分层数据结构,但不幸的是,对于一些数据,分层结构太深,以至于我得到了一个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循环中这样做时,我可以使用什么技术来使递归更容易?

编辑:

在下面的答案的帮助下,我能够极大地简化事情。该代码现在几乎与原始递归版本一样简洁。现在,我只需要在其他地方应用相同的原理,在相同的数据结构上递归。

共有3个答案

唐法
2023-03-14

好吧,我要用人类的语言来解释,因为我现在不想编码:

  1. 获取最高级别的元素并写入列表

你只需要在循环头放一个布尔值,如果孩子列表中没有元素了,就把它设置为false...我希望我能正确表达自己,随时提问和/或询问澄清。

这个算法将以指数级的速度变慢(--

鄂和璧
2023-03-14

基本上就是对一棵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();
  }
}
乐正瑞
2023-03-14

由于您处理的是树结构,并且希望计算累积大小,因此在跟踪每个节点的父节点时,请尝试使用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的建议编辑我使用以下方法: 这有助于获取每个第一个的。现在发生的情况是以下子元素没有填充。