直接的な方法の1つは、呼び出しごとに次のことを行う再帰的な手順です。プロシージャへの入力は、すでに選択されているペアのリストと、すべてのペアのリストです。
- 入力リストでまだカバーされていない最小数を計算します。最初の呼び出しでは、ペアが選択されていないため、これはもちろん0になります。
- すべての番号がカバーされている場合、正しい組み合わせがあり、それを印刷して前の手順に戻ります。それ以外の場合、明らかにされている最小の数は、私たちが目指している目標です。
- ターゲット番号をカバーする方法を探してペアを検索します。存在しない場合は、前のレベルの再帰に戻ります。
- ターゲット番号をカバーする方法がある場合は、最初の方法を選択し、選択したペアを選択したペアのリストに追加して、プロシージャ全体を再度再帰的に呼び出します。
- それが戻ったら、ターゲット番号をペアでカバーする次の方法を探します。以前に選択したペアと重複することはありません。見つかった場合は、それを選択して、次のプロシージャを再帰的に再度呼び出します。
- ターゲット番号をカバーする方法がなくなるまで、ステップ4と5を続けます。ペアのリスト全体を調べます。正しい選択肢がなくなったら、再帰の前のレベルに戻ります。
このアルゴリズムを視覚化する方法は、パスが重複しないペアのシーケンスであるツリーを使用することです。ツリーの最初のレベルには、0を含むすべてのペアが含まれます。上記の例では、ツリーは
ルート
|
----------------
| | |
(0,1)(0,2)(0,3)
| | |
(2,3)(1,3)(1,2)
この例では、ツリーを通るすべてのパスが実際に正しいコレクションを提供しますが、たとえばペア(1,2)を省略した場合、右端のパスにはノードが1つしかなく、手順3での検索に失敗します。
このタイプの検索アルゴリズムは、特定のタイプのすべてのオブジェクトを列挙する多くの同様の問題に対して開発できます。
おそらくOPが意味するのは、質問にあるように、それらのセットだけではなく、すべてのペアが入力に含まれていることを示唆している。その場合、どのペアが許可されているかを確認する必要がなくなるため、アルゴリズムははるかに簡単になります。すべてのペアのセットを生成する必要さえありません。次の擬似コードは、OPが要求したことを行います。ここで、は入力番号、「list」は空のリストから始まり、「covered」は0に初期化された長さnの配列です。多少効率を上げることができますが、それは私の目標ではありません。nn
sub cover {
i = 0;
while ( (i < n) && (covered[i] == 1 )) {
i++;
}
if ( i == n ) { print list; return;}
covered[i] = 1;
for ( j = 0; j < n; j++ ) {
if ( covered[j] == 0 ) {
covered[j] = 1;
push list, [i,j];
cover();
pop list;
covered[j] = 0;
}
}
covered[i] = 0;
}