将循环(while / for)转换为递归或从递归转换为循环的一般方法?


23

这个问题主要集中在算法上,也许是抽象的并且更学术化。

这个例子提供了一个想法,我想用一种通用的方式,所以例子仅用于使我们更清楚地了解您的想法。

一般来说,循环可以转换为递归的。

例如:

for(int i=1;i<=100;++i){sum+=i;}

其相关的递归为:

int GetTotal(int number)
{
   if (number==1) return 1;   //The end number
   return number+GetTotal(number-1); //The inner recursive
}

最后,为了简化此过程,需要执行尾递归:

int GetTotal (int number, int sum)
{
    if(number==1) return sum;
    return GetTotal(number-1,sum+number);
}

但是,大多数情况下都不容易回答和分析。我想知道的是:

1)我们能否获得一种“通用方式”将循环(for / while……)转换为递归?在进行转换时,我们应该注意哪些事情?最好写一些示例的详细信息以及您的persudo理论以及转换过程。

2)“递归”具有两种形式:线性递归和尾递归。那么哪个转换更好呢?我们应该掌握什么“规则”?

3)有时我们需要保留递归的“历史”,这很容易在循环语句中完成:

例如:

List<string> history = new List<string>();
int sum=0;
for (int i=1;i<=100;++i)
{
   if(i==1) history.Add(i.ToString()+"'s result is:1.");
   else
   {
     StringBuilder sub = new StringBuilder();

      for(int j=1;j<=i;++j)
      {
          if(j==i) sbu.Append(j.ToString());
          else
          {
            sub.Append(j.ToString()+"+");
          }
      }
    sum +=i;
    sbu.Append("'s result is:"+sum+Environment.NewLine);
   }
}

结果如下:

1的结果是1。

1 + 2的结果是3。

1 + 2 + 3的结果是6…………

但是,我认为很难将历史保持在递归状态,因为基于递归的算法着重于获取最后的结果并进行回调。因此,所有这些操作都是通过由编程语言维护的堆栈来完成的,该语言由堆栈自动分配内存。以及我们如何“手动”取出每个“堆栈值”并通过递归算法返回多个值?

那么“从递归算法到循环”又如何呢?它们可以彼此转换吗(我认为应该从理论上完成,但是我想要更准确的方法来证明我的想法)


“ persudo”是什么意思?
t

Answers:


30

实际上,您应该先分解功能:

循环包含几个部分:

  1. 标头,并循环之前进行处理。可能会声明一些新变量

  2. 条件,何时停止循环。

  3. 实际的循环体。它更改某些标头的变量和/或传入的参数。

  4. 尾巴; 循环并返回结果后会发生什么。

或写出来:

foo_iterative(params){
    header
    while(condition){
        loop_body
    }
    return tail
}

使用这些块进行递归调用非常简单:

foo_recursive(params){
    header
    return foo_recursion(params, header_vars)
}

foo_recursion(params, header_vars){
    if(!condition){
        return tail
    }

    loop_body
    return foo_recursion(params, modified_header_vars)
}

Etvoilà; 任何循环的尾部递归版本。循环主体中的breaks和continues仍必须替换为,return tailfoo_recursion(params, modified_header_vars)根据需要返回,但这很简单。


走另一条路更复杂。部分原因是可以有多个递归调用。这意味着,每次我们弹出堆栈框架时,都会有多个需要继续的地方。此外,在递归调用和调用的原始参数中可能还需要保存一些变量。

我们可以使用开关来解决此问题:

bar_recurse(params){
    if(baseCase){
        finalize
        return
    }
    body1
    bar_recurse(mod_params)
    body2
    bar_recurse(mod_params)
    body3
}


bar_iterative(params){
    stack.push({init, params})

    while(!stack.empty){
        stackFrame = stack.pop()

        switch(stackFrame.resumPoint){
        case init:
            if(baseCase){
                finalize
                break;
            }
            body1
            stack.push({resum1, params, variables})
            stack.push({init, modified_params})
            break;
        case resum1:
            body2
            stack.push({resum2, params, variables})
            stack.push({init, modified_params})
            break;
        case resum2:
            body3
            break;
        }
    }
}

0

在@ratchet freak的答案之后,我创建了这个示例,说明如何在Java中将Fibonacci函数重写为while循环。请注意,有一种更简单(有效)的方式可以通过while循环重写斐波那契。

class CallContext { //this class is similar to the stack frame

    Object[] args;

    List<Object> vars = new LinkedList<>();

    int resumePoint = 0;

    public CallContext(Object[] args) {
        this.args = args;
    }

}


static int fibonacci(int fibNumber) {
    Deque<CallContext> callStack = new LinkedList<>();
    callStack.add(new CallContext(new Object[]{fibNumber}));
    Object lastReturn = null; //value of last object returned (when stack frame was dropped)
    while (!callStack.isEmpty()) {
        CallContext callContext = callStack.peekLast();
        Object[] args = callContext.args;
        //actual logic starts here
        int arg = (int) args[0];
        if (arg == 0 || arg == 1) {
            lastReturn = arg;
            callStack.removeLast();
        } else {
            switch (callContext.resumePoint) {
                case 0: //calculate fib(n-1)
                    callStack.add(new CallContext(new Object[]{arg - 1}));
                    callContext.resumePoint++;
                    break;
                case 1: //calculate fib(n-2)
                    callContext.vars.add(lastReturn); //fib1
                    callStack.add(new CallContext(new Object[]{arg - 2}));
                    callContext.resumePoint++;
                    break;
                case 2: // fib(n-1) + fib(n-2)
                    callContext.vars.add(lastReturn); //fib2
                    lastReturn = (int) callContext.vars.get(0) + (int) callContext.vars.get(1);
                    callStack.removeLast();
                    break;
            }
        }
    }
    return (int) lastReturn;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.