web-dev-qa-db-ja.com

再帰関数は反復/ループを持つことができますか?

私は再帰関数について研究してきましたが、明らかに、それらは自分自身を呼び出し、反復/ループを使用しない関数です(そうでなければ、再帰関数ではありません)。

しかし、例としてWebサーフィンをしているときに(8クイーンの再帰的な問題)、次の関数を見つけました。

private boolean placeQueen(int rows, int queens, int n) {
    boolean result = false;
    if (row < n) {
        while ((queens[row] < n - 1) && !result) {
            queens[row]++;
            if (verify(row,queens,n)) {
                ok = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}

whileループが関係しています。

...なので、今は少し迷っています。ループを使用できますか?

12
Omega

あなたは再帰を誤解しました:反復を置き換えるために使用できますが、再帰関数がそれ自体の内部に反復を持たないようにするための要件は絶対にありません。

関数が再帰的と見なされるための唯一の要件は、関数がそれ自体を直接または間接的に呼び出すコードパスの存在です。すべての正しい再帰関数には、ある種の条件もあり、永久に「再帰」するのを防ぎます。

再帰関数は、バックトラックを使用した再帰検索の構造を示すのに理想的です。まず、終了条件のチェックrow < nに戻り、再帰のレベルで検索を決定します(つまり、クイーン番号rowの可能な位置を選択します)。各反復の後、関数がこれまでに見つけた構成に基づいて再帰呼び出しが行われます。最終的には、rowレベルの再帰呼び出しでnnに到達すると、「ボトムアウト」します。

41
dasblinkenlight

再帰関数の一般的な構造は次のようなものです:

myRecursiveFunction(inputValue)
begin
   if evaluateBaseCaseCondition(inputValue)=true then
       return baseCaseValue;
   else
       /*
       Recursive processing
       */
       recursiveResult = myRecursiveFunction(nextRecursiveValue); //nextRecursiveValue could be as simple as inputValue-1
       return recursiveResult;
   end if
end

/*recursive processing*/としてマークしたテキストは何でもかまいません。それはcouldループを含み、解決される問題がそれを必要とする場合、ループを含み、myRecursiveFunctionへの再帰呼び出しを含むこともできます。

あなたは確かに再帰関数でループを使用することができます。関数を再帰的にするのは、関数が実行パスのある時点で自分自身を呼び出すということだけです。ただし、関数が返すことができない無限再帰呼び出しを防ぐための条件が必要です。

6
marco-fiset

再帰呼び出しとループは、反復計算を実装するための2つの方法/構成にすぎません。

whileループは末尾再帰呼び出し(たとえば here を参照)、つまり2つの反復間の中間結果(1つのすべての結果を保存する必要がない反復)に対応しますサイクルは、次のサイクルに入るときに準備ができています)。後で再び使用できる中間結果を保存する必要がある場合は、whileループをスタックと組み合わせて使用​​するか( here を参照)、または末尾再帰以外(つまり任意) )再帰呼び出し。

多くの言語では、両方のメカニズムを使用でき、自分に適したものを選択して、コードでそれらを組み合わせることができます。 C、C++、Javaなどの命令型言語では、スタックが不要な場合は通常whileまたはforループを使用し、スタックが必要な場合は再帰呼び出しを使用します(暗黙的にランタイムスタックを使用します)。 Haskell(関数型言語)は反復制御構造を提供しないため、反復を使用して反復を実行することしかできません。

あなたの例では(私のコメントを参照):

// queens should have type int [] , not int.
private boolean placeQueen(int row, int [] queens, int n)
{
    boolean result = false;
    if (row < n)
    {
        // Iterate with queens[row] = 1 to n - 1.
        // After each iteration, you either have a result
        // in queens, or you have to try the next column for
        // the current row: no intermediate result.
        while ((queens[row] < n - 1) && !result)
        {
            queens[row]++;
            if (verify(row,queens,n))
            {
                // I think you have 'result' here, not 'ok'.
                // This is another loop (iterate on row).
                // The loop is implemented as a recursive call
                // and the previous values of row are stored on
                // the stack so that we can resume with the previous
                // value if the current attempt finds no solution.
                result = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}
1
Giorgio

再帰と反復またはループの間に関係があると考えるのは当然です。多くの場合、再帰アルゴリズムは手動で、またはテールコール最適化を使用して反復ソリューションに自動的に変換されます。

8つのクイーンでは、再帰部分はバックトラッキングに必要なデータの保存に関連しています。再帰について考えるとき、スタックにプッシュされるものについて考えることは価値があります。スタックには、アルゴリズムで重要な役割を果たす値渡しパラメータとローカル変数、または場合によってはリターンアドレスのようにそれほど関連性のないもの、またはこの場合は、使用されるクイーンの数を含む渡された値を含めることができます。アルゴリズムによって変更されません。

8つのクイーンで発生するアクションは、本質的に、最初の数列のクイーン数の部分的な解が与えられ、そこから現在の列で有効な限り遠い選択肢を繰り返し決定して、残りの列。ローカルでは、8つのクイーンが試行中の行を追跡し、バックトラッキングが発生した場合、残りの行をステップ実行するか、機能する可能性のある他の行が見つからない場合に戻るだけで、さらにバックトラックできます。

1
DeveloperDon

「問題の小さいバージョンを作成する」部分にはループがある場合があります。メソッドがそれ自体を呼び出し、問題の小さいバージョンをパラメーターとして渡す限り、メソッドは再帰的です。もちろん、スタックオーバーフロー状態を回避するために、問題の最小バージョンが解決され、メソッドが値を返す場合の終了条件を提供する必要があります。

あなたの質問の方法は再帰的です。

0

再帰は基本的に関数を再度呼び出します。再帰の主な利点はメモリを節約することです。再帰にはループがあり、他の操作を実行するために使用されます。

0
Akshay