web-dev-qa-db-ja.com

javaで再帰を抜け出す

再帰は一種の「分割統治」スタイルであり、小さくなりながら分割され(ツリーデータ構造)、違反が見つかった場合は完全に中断します。つまり、すべての再帰パスを中断し、trueを返します。これは可能ですか?

25
Stasis

エラーコードを返すか、グローバル変数を変更して、各再帰インスタンスが「自分自身を殺す」ことを認識できるようにすることができます。

一種の何か。

int foo(bar){
     int to_the_next;

      if (go_recursive){
            to_the_next = foo(whisky_bar);

            if (to_the_next ==DIE) return DIE;
      }

      if (something_unexpected_happened) return DIE;

      process;//may include some other recursive calls, etc etc
}
17
Tom

何をしても、スタックを解く必要があります。これには2つのオプションがあります。

  1. マジックの戻り値(トムの1人が説明)
  2. 例外をスローします(thaggieによる言及)。

あなたが物事を死なせたいケースがまれである場合、これは例外を投げることが実行可能な選択であるかもしれない状況の1つであるかもしれません。そして、誰もがこのことに夢中になる前に、プログラミングの最も重要なルールの1つが、ルールを破るのが適切な時期を知ることであることを覚えておいてください。

結局のところ、今日はGoogleコードからzxingライブラリを評価しました。実際には、多くの制御構造で例外スローを使用しています。それを見たときの第一印象は恐怖でした。それらは文字通り、メソッドが例外をスローしないまで、異なるパラメーターで数万回メソッドを呼び出していました。

これは確かにパフォーマンスの問題のように見えたので、魔法の戻り値を使用するように変更するためにいくつかの調整を行いました。そして、あなたは何を知っていますか?デバッガーで実行すると、コードが40%高速になりました。しかし、非デバッグに切り替えると、コードは1%未満速くなりました。

この場合でも、フロー制御に例外を使用する決定に夢中です(つまり、例外がスローされますall時間)。しかし、ほとんど計り知れないパフォーマンスの違いがあるため、再実装する価値はありません。

反復の終了をトリガーする条件がアルゴリズムの基本的な部分ではない場合、例外を使用すると、コードがよりクリーンになる場合があります。私にとって、この決定を下すのは、再帰全体を展開する必要がある場合は例外を使用することです。再帰の一部のみをほどく必要がある場合は、魔法の戻り値を使用します。

38
Kevin Day

再帰を実行する単一スレッドの場合は、例外をスローする可能性があります。少し醜いですが、例外としてgotoとして使用しています。

boolean myPublicWrapperMethod(...) {
    try {
        return myPrivateRecursiveFunction(...);
    } catch (MySpecificException e) {
        return true;
    } 
} 

より良いアプローチは、再帰を排除し、再帰的な状態であるものを表すクラスを保持するStackコレクションを使用し、ループで反復し、必要なときにtrueを返すことです。

5
Tom

例外処理をお勧めします。これは、何らかの違反(またはその他の例外)が原因で再帰が打ち切られたことを明らかにしています。

public void outer() {
  try {
    int i = recurse(0);
  } catch (OuchException ouch) {
    // handle the exception here
  }
}

private int recurse(int i) throws OuchException {

    // for tree-like structures
    if (thisIsALeaf) {
       return i;
    }

    // the violation test
    if (itHurts)
       throw new OuchException("That really hurts");

    // we also can encapsulate other exceptions
    try {
       someMethod();
    } catch (Exception oops) {
       throw new OuchException(oops);
    }

    // recurse
    return recurse(i++);
}

確かに、それは中絶時に「true」を返すという初期要件に違反しています。しかし、私は戻り値と異常な動作に関する通知を明確に分離することを好みます。

4
Andreas_D

再帰が壊れるかどうかを追跡する変数を格納することで、同様のことを行うことができます。残念ながら、すべての再帰を確認する必要がありますが、実行できます。

1
Joe Phillips

あなたが求めているのは、再帰の定義です。

ある時点で、すべての再帰パスが壊れるはずです。そうでなければ、それは無限の再帰となり、 スタックオーバーフロー例外 が発生します。

したがって、そのような再帰関数をdesignする必要があります。例 ソートされた配列のバイナリ検索

BinarySearch(A[0..N-1], value, low, high) {
       if (high < low)
           return -1 // not found
       mid = low + ((high - low) / 2)  // Note: not (low + high) / 2 !!
       if (A[mid] > value)
           return BinarySearch(A, value, low, mid-1)
       else if (A[mid] < value)
           return BinarySearch(A, value, mid+1, high)
       else
           return mid // found
}
1
Emre

エラーが発生したときに再帰ループから抜け出す最善の方法は、ランタイム例外をスローすることです。

throw new RuntimeException("Help!  Somebody debug me!  I'm crashing!");

もちろん、これはプログラムを強制終了しますが、再帰によってこのような例外がスローされないことを確認するには、範囲チェックとアルゴリズム分析を使用する必要があります。再帰的アルゴリズムから抜け出したくなる理由の1つは、メモリが不足していることです。ここで、アルゴリズムがスタックで使用するメモリの量を決定できます。 Javaをコーディングしている場合、たとえば、その計算を

getMemoryInfo.availMem().

N!を見つけるために再帰を使用しているとしましょう。関数は次のようになります。

public long fact(int n)
{
    long val = 1;
    for (int i  = 1, i<=n,i++)
        return val *= fact(i);
    return val;
}

実行する前に、スタック全体を保持するために、メモリ内に(長いバイト数、Javaでは8バイト)* nバイトあることを確認してください。基本的に、再帰的なメソッド/関数の前に範囲とエラーをチェックすれば、ブレークアウトする必要はありません。ただし、アルゴリズムによっては、スタック全体に送信してもよいことを通知する必要がある場合があります。その場合は、トムの答えはうまくいきます。

0
samdoj

再帰呼び出しが並行して評価されている場合を除いて、おそらく、2番目の再帰呼び出し(その後、バイナリツリー構造でない場合)の再帰呼び出しを行う前に、最初の再帰呼び出しの値をチェックするロジックを追加する必要があるだけです。

public abstract class Tree {

    protected abstract boolean headIsViolation();

    protected abstract boolean isLeaf();

    public Tree getLeft();

    public Tree getRight();

    // Recursive
    public boolean checkViolation() {
        if(this.isLeaf()) {
            return this.headIsViolation();
        }

        // If needed, you could pass some sort of 'calculation state'
        // object through the recursive calls and evaluate the violation
        // through that object if the current node is insufficient
        if(this.headIsViolation()) {
            // Terminate the recursion
            return true;
        }

        // Fortunately, Java short-circuits ||
        // So if the left child is in violation, the right child will
        // not even be checked
        return this.getLeft().checkViolation() || this.getRight().checkViolation();
    }
}
0
Greg Case

たぶん、再帰を避けてスタックに置き換えたいでしょう。これにより、再帰的な操作に似た操作を実行しながら、操作を中断するコントロールが提供されます。実際、自分で再帰をエミュレートするだけです。

私はここに素敵な説明を見つけました: http://haacked.com/archive/2007/03/04/Replacing_Recursion_With_a_Stack.aspx/

0
murphy

グローバル変数を使用して、すべての再帰呼び出しから抜け出すことができました。

boolean skipRecursivePaths = false;
private callAgain(){

if(callAgain){
  if(getOutCompletely){

    skipRecursivePaths = true;
    }
    if(skipRecursivePaths){
    return;
    }
}
0
roho