再帰関数に反復/ループを含めることはできますか?


12

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

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

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関係ループが。

...だから私は少し迷っています。ループを使用できますか?


5
コンパイルしますか。はい。なぜ尋ねるのですか?
トーマスエディング

6
再帰の完全な定義は、ある時点で、関数はそれが戻る前にそれ自身の実行の一部として再呼び出しされる可能性があるということです(それ自体で呼び出されるか、他の関数によって呼び出されるか)。その定義については、ループの可能性を排除するものはありません。
cHao

cHaoのコメントの補遺として、再帰関数はそれ自体のより簡単なバージョンで再呼び出しされます(そうでない場合、永久にループします)。オーブリングを引用するには(普通の英語では、再帰とは何ですか?):「再帰プログラミングは、問題を段階的に減らして、それ自体のバージョンを解決しやすくするプロセスです。」この場合、最も困難なバージョンplaceQueenは「8クイーンを配置」で、より簡単なバージョンplaceQueenは「7クイーンを配置」(その後6桁など)
ブライアン

Omegaで動作するものなら何でも使用できます。ソフトウェア仕様が使用するプログラミングのスタイルを指定することはめったにありません-学校にいて、課題がそう言う場合を除きます。
Apoorv Khurasia

@ThomasEding:はい、もちろんコンパイルして動作します。しかし、私は現時点でエンジニアリングを勉強しているだけです。この時点で重要なのは、厳密な概念/定義であり、今日のプログラマの採用方法ではありません。だから、私が持っている概念が正しいかどうかを尋ねています(そうではないようです)。
オメガ

回答:


41

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

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

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


1
あわやそこにそうでないから「正しい」再帰関数が不正なものの条件、たくさんあるため1
ジミー・ホッファ

6
+1 "recurring down" forever `Turtle(){Turtle();}
ミスター・ミンダー

1
@ Mr.Mindor「カメはずっと下にいる」という引用が
大好きです

それは私を笑顔にしました:
Martijn Verburg

2
「すべての正しい再帰関数には、何らかの条件があり、永久に「再帰」することを防ぎます。」厳密でない評価では当てはまりません。
パブ

12

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

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*/何でもかまいません。 解決する問題がそれを必要とする場合、ループを含めることができますmyRecursiveFunction。また、への再帰呼び出しを含めることもできます。


1
再帰呼び出しが1つしかないことを意味し、再帰呼び出し自体がループ内にある場合(Bツリートラバーサルなど)をほとんど除外するため、それは誤解を招きます。
ピーターテイラー

@PeterTaylor:はい、私はそれをシンプルにしようとしていました。
FrustratedWithFormsDesigner

または、各ノードが2つの子を持つために2つの呼び出しがある、プレーンなバイナリツリーを走査するような、ループのない複数の呼び出しですらあります。
イズカタ

6

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


1

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

while末尾再帰呼び出しにループ対応は、(例えば参照ここでは)、つまりは、あなたが2回の反復の間の中間結果を保存する必要のない、反復(あなたが次のサイクルに入る時に1サイクルのすべての結果は、準備ができています)。後で再び使用できる中間結果を保存する必要がある場合は、whileループをスタックとともに使用できます(こちらを参照してください))をか、非末尾再帰(つまり、任意の)再帰呼び出しを使用できます。

多くの言語では、両方のメカニズムを使用できます。また、より適切なメカニズムを選択し、コード内でそれらを組み合わせることもできます。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

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

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

8つのクイーンで発生するアクションは、基本的に、最初のいくつかの列でいくつかのクイーンの部分解が与えられ、そこから評価のために再帰的に渡される現在の列の有効な選択肢を繰り返し決定することです残りの列。ローカルでは、8人のクイーンが試行している行を追跡し、バックトラッキングが発生した場合、残りの行をステップスルーするか、動作する他の行が見つからない場合は単に戻ることでさらにバックトラックする準備ができています。


0

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

質問のメソッドは再帰的です。


0

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


異なるように頼む。多くのアルゴリズムは再帰的または反復的である可能性があり、スタックにプッシュする必要がある戻りアドレス、パラメーター、およびローカル変数をカウントする場合、再帰的ソリューションは多くの場合、より多くのメモリを消費します。一部の言語は、末尾再帰または末尾呼び出しの最適化を検出して最適化するのに役立ちますが、これは言語またはコード固有の場合があります。
DeveloperDon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.