您如何将其转换为迭代函数而不是使用嵌套循环进行递归?

Posted

技术标签:

【中文标题】您如何将其转换为迭代函数而不是使用嵌套循环进行递归?【英文标题】:How would you convert this to an iterative function instead of recursive with a nested loop? 【发布时间】:2019-08-20 10:34:18 【问题描述】:

以下函数导致数百个递归级别。我想知道是否有人对如何将其切换为循环功能提出建议。我相信在这种情况下执行顺序确实很重要,但我可能需要做更多的调查。我希望我可以直接将它转换为迭代函数而不是递归。

正如您所希望看到的,传递给每个递归级别的唯一参数是“after”。所以递归函数调用本身比较简单,但是调用周围的循环让我很反感。

我考虑过排队,但“已更改”的条件似乎意味着深度优先检查。在将其添加到队列之前,我必须执行部分移位操作,但在当前代码中,下一级递归将立即开始,所以我不能只建立一个项目队列来处理和执行按顺序排列。

我考虑过一个堆栈,但我不确定如何实现它来替换递归。

我决定简化代码,因为它可能有点混乱。这是一个骨架(如果您初始化变量,您实际上可以运行它!)可能更像是“伪”

    private void DataChangedRecursive(LinkedNode node)
    
        InitializeVariables();
        try
        
            foreach (LinkedNode after in node.After)
            
                var afterDetails = after.Before;
                bool changed = CheckData(afterDetails);

                if (changed)
                
                    DataChangedRecursive(afterDetails);
                
            
        
        catch 
        
            // Assume relavant error handling at this level in the stack.  This probably isn't important to maintain, but it'd be interested if we could.
            throw;  
        
    
    
    public object InitializeVariables()
    
        // Assume relavant work happens here.
        return new object();
    
    
    public bool CheckData(LinkedNode dr)
    
        // Logic is that something changes, so it needs to save.  This does a bunch of comparisons on the current item.
        return dr.DataChanged;
    
    
    public class LinkedNode
    
        public LinkedNode Before get;set;
        public bool DataChanged get;set;
        public List<LinkedNode> After get;set;
    

【问题讨论】:

出于兴趣:为什么解决方案首先被实现为递归函数? 这是一个很好的问题,值得一个很好的答案和很多赞成! @Zimano 我不确定。这是我们解决方案中最古老的代码块之一。它的工作方式是每个时间表都链接到前任和后处理器。我假设他们确实递归认为这是按特定顺序遍历到下一个级别的最简单方法。我把它画在一块板上,它基本上就像一棵二叉树,最右边的分支首先完成,然后下一个直接分支离开。这是它如何遍历的图片:link。 查看它的最佳方式是它在完成父节点之前执行子节点。在处理根时,它移动到右孩子,然后在完成右孩子之前,完成它的右孩子,等等。直到所有子节点都完成,然后运行父节点。 在 drPost.GetParentRow("schedulePredecessors") 为 null 之前执行一个 while 循环怎么样......这意味着你应该停止?......继续向上移动直到你到达根节点?跨度> 【参考方案1】:

内存中有一个部分,称为stack。函数调用存储在堆栈中。考虑到您的 f 函数调用了 g 函数,因此 g 函数被推入堆栈。当 g 被执行时,它将从堆栈中弹出,其结果(如果有)将返回到 f 被中断调用并执行 g 的位置的 f。

这种行为是自动的,但如果你理解它——我建议你阅读一些关于它的文章——那么你就会意识到你需要模仿什么行为。是的,您认为需要堆栈是正确的。堆栈中的每个项目都必须存储状态,也就是说,您需要确保每当您将新项目推送到堆栈时,您不会忘记在将新项目推送到堆栈之前您所处的中断项目的状态堆栈。此外,当你从堆栈中弹出一个项目时,你需要确保它的结果(在这种情况下你有 void,所以你不会关心结果,但我是在笼统地说)被正确返回给弹出后的新栈顶。您还需要在弹出后准确地恢复到正确的状态。所以你需要小心处理你的变量。

现在,让我们看看它应该如何工作。对于每个级别,您都有一个潜在的新级别队列。在我们的例子中,您将需要一个循环来处理每次迭代中的一个步骤。步骤可以是初始化、队列项处理、弹出。该循环将一直运行到堆栈变空,因此请确保您的堆栈最终会变空以避免一些挫折。要知道您需要做什么,您需要为堆栈的每个级别设置一个状态,因此您的程序将始终知道给定项目是否已经初始化,它的队列是否正在迭代或是否要完成。队列是可能的子项的集合。一个非常简化的伪代码来说明这个想法:

root.state <- preinitialized
stack.push(root);
while (not stack.empty()) do
    item <- stack.top()
    if (item.state = preinitialized) then
        item.initializeQueue()
        item.queueIterator.initialize()
        item.state <- queue
    else if (item.state = queue) then
        if (item.queueIterator.hasNext()) then
            next <- item.queueIterator.next()
            if (someCondition) then
                next.state <- preinitialized
                stack.push(next)
            end if
        else
            item.state <- finished
            result <- stack.pop(item)
            if (stack.top()) then
                stack.top.apply(result)
            else
                finalResult <- result
            end
        end if
    end if 
end while

【讨论】:

如果它不是递归函数内的循环,那将起作用。答案并不是那么微不足道。对于每一级递归,可能有无限数量的子级也会触发另一级递归。直接转换为堆栈是不可能的......我能想到的最接近的事情是使用队列,但是时间表的顺序保持不变,但处理的顺序会有所不同。 @Knodel12 似乎我没有以可以理解的方式制定我的答案。我的意思是我们有一个堆栈,并且在每个级别上我们都有一个队列。如果您在记住这一点的同时再次查看伪代码,您会意识到我的回答实际上解决了您提出的问题。但是,必须纠正它被误解的事实,因此,无论答案中不清楚或在语法上具有误导性,请告诉我,以便我重新整理这些部分以使其更易于理解。 我正在努力实施您的解决方案。什么是队列迭代器?什么是状态? 另外,什么是适用的?结果呢? @Knodel12 queueIterator 是一个迭代器,您可以使用它来迭代队列。由于在 C# 中有无数种使用队列的方法,我相信您选择应该如何处理它。但是由于queueIterator显然不清楚,我认为给你一个例子是可行的。假设您正在使用 Queue 类。在这种情况下,您可以通过 Enqueue 方法将元素添加到队列的末尾,并通过 Dequeue 方法从队列的头部获取元素。【参考方案2】:

终于想通了,顿悟了。事实证明,处理递归和循环的最简单方法是利用 IEnumerator。它要求您手动处理迭代(没有 for 循环),但递归和循环的顺序将是相同的。

我将函数分为两部分,入口函数和执行迭代以及“递归”的函数。从本质上讲,这将保证所有子节点首先完全完成,就像递归一样,并返回父节点中正确的迭代点。理论上,这应该适用于任何内部带有循环的递归函数。

private void DataChangedRecursive(LinkedNode node)

    try
    
        DataChanged(node);
    
    catch 
    
        throw;  
    


private void DataChanged(LinkedNode node)

    var state = new Stack<IEnumerator<LinkedNode>>();
    state.Push(node.After.GetEnumerator());

    while (state.Count > 0)
    
        InitializeVariables();
    
        while (state.Peek().MoveNext())
        
            ItemWithPredPostcessor after = state.Peek().Current;
            ItemWithPredPostcessor afterDetails = after.Before;
            bool dataChanged = StartShift(afterDetails);

            if (dataChanged)
            
                Save(afterDetails);
                state.Push(afterDetails.After.GetEnumerator());
            
        
        state.Pop(); // Remove current from list, as we've completed.
    

【讨论】:

以上是关于您如何将其转换为迭代函数而不是使用嵌套循环进行递归?的主要内容,如果未能解决你的问题,请参考以下文章

在R中将嵌套的for循环转换为并行

递归 C# 函数从 for 循环内部返回 - 如何转换为 F#?

Python 3.3 如何将此递归函数转换为纯 yield 循环版本?

递归转换为迭代的一种通用方式

如何在python中将迭代函数转换为递归函数

应该选择递归而不是迭代?