web-dev-qa-db-ja.com

ループ(while / for)を再帰に、または再帰からループに変換する一般的な方法は?

この問題は主にアルゴリズムに焦点を合わせており、抽象的でより学術的なものかもしれません。

例は考えを提供しています。私は一般的な方法を使いたいので、例はあなたの考えについてより明確にするためにのみ使用されます。

一般的に言えば、ループは再帰的に変換できます。

例えば:

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) "Recursive"には、Linely recursiveとTail-Recursiveの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…………

ただし、履歴を再帰的に保持することは難しいと思います。再帰ベースのアルゴリズムは、最後の結果を取得してコールバックを返すことに重点を置いているためです。したがって、これらすべては、プログラミング言語によって維持されているスタックを介して行われ、メモリはスタックの形で自動的に割り当てられます。そして「手動で」各「スタック値」を取り出して、再帰的アルゴリズムを通じて複数の値を返す方法は?

そして「再帰的アルゴリズムからループへ」についてはどうですか?それらは相互に変換できますか(理論的には行われるべきだと思いますが、私の考えを証明するためにより正確なことを望んでいます)

25
xqMogvKW

実際には、最初に関数を分解する必要があります。

ループにはいくつかの部分があります:

  1. ヘッダー、および処理beforeループ。いくつかの新しい変数を宣言できます

  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 tail_に置き換える必要があり、必要に応じてfoo_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;
        }
    }
}
_
33
ratchet freak

@ratchet freakの回答に続き、フィボナッチ関数をJavaの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;
}
1
Yamcha