web-dev-qa-db-ja.com

再帰的なバックトラックはどのように機能しますか?

私は再帰的なバックトラックを理解しようとしています、私は再帰について十分に理解しており、ある程度までバックトラックの概念も理解していますが、forループが次のコードで使用されているときに物事がどのように機能するかの時系列順を理解するのは困難です。

public static void diceRolls(int dice) { 
    List<Integer> chosen = new ArrayList<Integer>(); 
    diceRolls(dice, chosen); 
} 

// private recursive helper to implement diceRolls logic 
private static void diceRolls(int dice,List<Integer> chosen) { 
     if (dice == 0) { 
          System.out.println(chosen); // base case 
       } else { 
          for (int i = 1; i <= 6; i++) { 
               chosen.add(i); // choose 
               diceRolls(dice - 1, chosen); // explore 
               chosen.remove(chosen.size() - 1); // un-choose 
      } 
  } 
}

今、私は問題を理解しています。たとえば、3をdiceRolls関数に渡し、ヘルパーメソッドを呼び出し、forループ内でiのすべての値(つまり1)を追加した後、メソッドを再度呼び出してforループを完了します再帰する前にそれ自体またはメソッドdiceRolls(2、chosen)が今渡されますか? 2が渡されると、ループも再帰する前に1回だけ実行されるためです。

5
Karan Singla

ダンジョンを歩いているかのようにバックトラックをイメージし、ダンジョン内のすべてのパスを探索する必要があります(ダンジョンは非循環です-選択したパスに関係なく、同じ場所に戻ることは決してありません)。複数の経路に分岐する場所に来たら、1つだけを選択してそれに従います。最後まで進んだら、最後のフォークに戻って次のパスを選択します。すべてのパスを探索したら、前の分岐点に戻り、ダンジョンの最初に戻るまで繰り返します。

コードでは、forサイクルはダンジョンのフォーク(この場合、続行できる6つの異なるパス)のようで、diceRollsへの再帰呼び出しは、可能なパスの1つ、ダンジョンの終わりを選択することを表します再帰を停止したときです(実行を停止せず、1つ前のステップに戻ります)。 diceRolls関数からジャンプするたびに、forループを続行して、再びdiceRollsを呼び出します。

最初の最初の反復で再帰呼び出しによってforループを中断し、111を出力して、1ステップ戻り、ループを続行すると、112、113、... 116、そして、1つ後ろに戻って121、122、123、...を取得し、661、662、666を取得します。この時点で、すべてのループと再帰呼び出しが終了し、計算が終了します。

7
OndroMih

以下を実行してみてください:

public static void diceRolls(int dice) { 
    List<Integer> chosen = new ArrayList<Integer>(); 
    diceRolls(dice, chosen); 
} 

// private recursive helper to implement diceRolls logic 
private static void diceRolls(int dice,List<Integer> chosen) {
System.out.println("Entering diceRolls with dice="+dice); 
     if (dice == 0) { 
          System.out.println(chosen); // base case 
       } else { 
          for (int i = 1; i <= 6; i++) {
               System.out.println("Starting for loop. i="+i+", dice="+dice); 
               chosen.add(i); // choose 
               diceRolls(dice - 1, chosen); // explore 
               chosen.remove(chosen.size() - 1); // un-choose 
               System.out.println("Ending for loop. i="+i+", dice="+dice);
           }
           System.out.println("Exiting diceRolls with dice="+dice); 
       }
}

Forループの反復ごとに、diceRolls(int dice、List selected)に対して新しい呼び出しが行われ、diceが以前より1つ少なくなります。サイコロが0でない場合、その新しい呼び出しにより、さらに6つの呼び出しが行われます。

バックトラックの考え方は、問題のすべての可能な解決策に対して深さ優先検索を実行することです。サイコロをN回振ろうとしているときに、1回の振るごとに数字を増やしていこうとしているとします。 [1,2,3]が1、2、3のロールを表すとします。問題の初期状態は[]で、ロールはまだ実行されていません。標準のサイコロは6つの異なる方法で振ることができるため、最初のロールの後に[1]、[2]、[3]、[4]、[5]、[6]になります。これらの状態のそれぞれについて、6つの方法でサイコロを振り、合計36の状態が可能です:[1,1]、[1,2]、[1,3]、[1,4]、[1 、5]、[1,6]、[2,1]、[2,2] ... [6,5]、[6,6]。ただし、ロールが増加していないため、[2,1]や[6,5]などのすでに無効な状態を「プルーニング」することができます。このようにして、問題のすべての可能な解が得られるまで、問題の各部分解に1つの追加変更を実行し、各ステップで枝刈りして問題のサイズを小さくすることができます。

1
chprice

OndreJMの答えは適切です。少し図式的な魔法を追加するために、単純な再帰的な階乗プログラムを検討し、フローチャートに従ってください。

enter image description here

1
leo