要素の繰り返しなしでペアのセットから組み合わせを生成する


28

ペアのセットがあります。各ペアの形式は(x、y)で、x、yは範囲の整数に属します[0,n)

したがって、nが4の場合、次のペアがあります。

(0,1) (0,2) (0,3)
(1,2) (1,3) 
(2,3) 

私はすでにペアを持っています。次に、n/2整数が繰り返されないようにペアを使用して組み合わせを作成する必要があります(つまり、各整数は最終的な組み合わせで少なくとも1回出現します)。理解を深めるための正しい組み合わせと間違った組み合わせの例を次に示します

 1. (0,1)(1,2) [Invalid as 3 does not occur anywhere]
 2. (0,2)(1,3) [Correct]
 3. (1,3)(0,2) [Same as 2]

ペアができたら、可能性のあるすべての組み合わせを生成する方法を誰かが提案できますか?


おそらく、2次元配列を使用してペアを表現することによって。有効な組み合わせは、選択されたn個の配列セルに対応し、各行と列に選択されたセルが1つだけ含まれます。
ジョー

4
入力はすべてのペアのセットであると言っていますか?その場合、入力は単にと言う必要があります。n
rgrig

2
あるであっても、常に?そうでない場合、「整数はどれも繰り返されません」と「各整数は最終的な組み合わせで少なくとも1回出現する」という文は矛盾しています。n
ドミトロKorduban

1
@rgrigと同じ問題:入力はすべて順不同のペアですか、それとも可能なペアの任意のセットですか?すべてのペアである場合は、入力がであると言うことができ、リストを指定する必要はありません。n
カベ

1
ペアの初期セットによって定義されたnポイントでグラフの完全な一致をすべて生成することに関心があります。さらに、あなたはそのグラフをそれらの点に関する完全なグラフとみなすようです。あなたがそれについて言及した場合、あなたの質問はより明確になるでしょう。ありますN - 1 = 1 × 3 × 5 × × n 1 そのようなマッチング。n(n1)!!:=1×3×5××(n1)
マークヴァンレーウェン

回答:


14

直接的な方法の1つは、呼び出しごとに次のことを行う再帰的な手順です。プロシージャへの入力は、すでに選択されているペアのリストと、すべてのペアのリストです。

  1. 入力リストでまだカバーされていない最小数を計算します。最初の呼び出しでは、ペアが選択されていないため、これはもちろん0になります。
  2. すべての番号がカバーされている場合、正しい組み合わせがあり、それを印刷して前の手順に戻ります。それ以外の場合、明らかにされている最小の数は、私たちが目指している目標です。
  3. ターゲット番号をカバーする方法を探してペアを検索します。存在しない場合は、前のレベルの再帰に戻ります。
  4. ターゲット番号をカバーする方法がある場合は、最初の方法を選択し、選択したペアを選択したペアのリストに追加して、プロシージャ全体を再度再帰的に呼び出します。
  5. それが戻ったら、ターゲット番号をペアでカバーする次の方法を探します。以前に選択したペアと重複することはありません。見つかった場合は、それを選択して、次のプロシージャを再帰的に再度呼び出します。
  6. ターゲット番号をカバーする方法がなくなるまで、ステップ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;
}

これは機能するはずですが、おそらく最も効率的な方法ではありません。
ジョー

2
最後に、ポイントはどういうわけかそのツリーのパスを列挙することです。入力リスト内のペアの数が可能なペアの数よりもはるかに少ない場合、特に各ステップでどの数がすでにカバーされているかを覚えるためにいくつかのハッシュテーブルが使用される場合、この種のアルゴリズムは完全に効率的です。これは一定時間で照会できます。
カール・ムンメルト

リストがポインターを使用している場合、KnuthのDancing Linksは一見の価値があります。再帰呼び出しからフォームを返し、リストの以前の状態を復元する必要がある場合。
ウリ

10

反復的に解決できます。範囲[ 0 n )のすべての解があるとします。その後、S nから解S n + 2を簡単に構築できます。サイズはnとともに非常に急速に大きくなるため、すべてのセットをメモリに保持するのではなく、ジェネレーターを作成する方がよい場合があります。以下のPythonの例を参照してください。Sn[0,n)Sn+2Snn

def pairs(n):
    if (n%2==1 or n<2):
        print("no solution")
        return
    if (n==2):
        yield(  [[0,1]]  )
    else:
        Sn_2 = pairs(n-2) 
        for s in Sn_2:
            yield( s + [[n-2,n-1]] )
            for i in range(n/2-1):
                sn = list(s)
                sn.remove(s[i])
                yield( sn + [ [s[i][0], n-2] , [s[i][1], n-1] ] )
                yield( sn + [ [s[i][1], n-2] , [s[i][0], n-1] ] )

を呼び出すことで、すべてのペアをリストできます

for x in pairs(6):
   print(x)

6

更新:私の以前の回答は、OPが求めていなかった2部グラフについて扱っていました。関連情報として、今のところそのままにしておきます。しかし、より適切な情報は、非二部グラフの完全な一致に関連しています。

この点で、Proppによる進行状況の概要を説明する素晴らしい調査があります(1999年まで)。その記事のアイデアのいくつか、および関連リンクは、役に立つとわかるかもしれません。TL; DRは-トリッキーです:)

---古い回答の始まり

あなたがやろうとしているのは、二部グラフ上で可能な完全一致をすべて列挙することです。これを行うには多くの異なるアルゴリズムがあり、特に最近のアルゴリズムの1つはISAAC 2001のものです。

基本的な考え方は、ネットワークフローを使用して完全に一致するものを1つ見つけてから、交互のサイクルを使用して繰り返しこれを変更することです(詳細については、ネットワークフローに関するアルゴリズムのテキストの章を参照してください)。


!場合にのみ(i = jの場合))二部グラフ)をN、0 [所与のラベルを持つ2つのセットからなり、エッジ(I、Jはあります
ジョー

nKn

2
パーマネントが答えを計算します。しかし、OPはそれらを列挙したい
スレシュ

それらはすべてグラフ構造のため同型であるため、順列の適用について考えるのは良い考えかもしれません(ただし、問題は重複を作成することです)。
カベ

4

選択したペアごとに、選択できなくなった2つの行が削除されます。このアイデアは、再帰アルゴリズムをセットアップするために使用できます(Scalaで)。

def combine(pairs : Seq[(Int,Int)]) : Seq[Seq[(Int, Int)]] = pairs match {
  case Seq() => Seq()
  case Seq(p) => Seq(Seq(p))
  case _ => {
    val combinations = pairs map { case (a,b) => {
      val others = combine(pairs filter { case (c,d) =>
        a != c && a != d && b != c && b != d
      })

      others map { s => ((a,b) +: s) }
    }}

    combinations.flatten map { _.sorted } distinct
  }
}

これは確かにより効率的な方法で表現できます。特に、組み合わせの行全体を考慮する必要がないという考えは、の呼び出しでは使用されませんfilter


これは、すべての数字を含まない組み合わせを返しますが、元のシーケンスにはそれらを拡張できるペアがないため、拡張できない組み合わせは返されませんか?その場合、それらの組み合わせを除外する必要があります。
カールムマート

n2N

(0,1)n=4

はい。しかし、私が言ったように、私の答えはOPが提案するシナリオ、すなわちarbitrary意的な入力ではありません。
ラファエル

私が元の質問を読んだとき、それは任意のペアのセットに関するものであり、OPはすべてのペアが可能であるとは決して言いません。しかし、私はOPがそれについてより明確にできることに同意します。
カール・ムンメルト

4

質問にはすでに多くの素敵な下水道がありますが、それらの背後にある基本的な一般的なトリックを指摘しておくといいと思います。

結合する要素の完全な順序付けが可能な場合、一意の組み合わせを生成するのははるかに簡単です。このように、ソートされた組み合わせのみを許可する場合、一意性が保証されます。ソートされた組み合わせを生成することも難しくありません-通常のブルートフォース列挙検索を行うだけですが、各ステップでは、各ステップで既に選択されている要素よりも大きい要素のみを選択します。

この特定の問題の余分な複雑さは、長さn / 2(最大長)の組み合わせのみを取得することです。適切な並べ替え戦略を決定する場合、これは難しくありません。たとえば、Carl Mummetの回答で指摘されているように、辞書式の並べ替え(図の上下左右)を考慮すると、常に次の要素を取得して、最初の桁がまだ使用されていない最小の番号。

他の長さのシーケンスを生成したい場合、この戦略を拡張することもできます。最初の数が利用可能な最小数ではない次の要素を選択するときはいつでも、ソートされたサブシーケンスに表示される要素の1つまたは複数の行を除外するため、prermutationの最大長はそれに応じて減少します。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.