ループ(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)「再帰」には、線形再帰と末尾再帰の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"はどういう意味ですか?
-gnat

回答:


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)
}

その他 ループの末尾再帰バージョン。ループ本体の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;
        }
    }
}

0

@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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.