1

アイテムのリストを取得し、リストを正確に 2 つのサブリストに分割した結果として得られる一意のサブセットをすべて生成する効率的なアルゴリズムを見つけようとしています。これを行う一般的な方法があると確信していますが、特定のケースに興味があります。リストはソートされ、重複するアイテムが存在する可能性があります。

いくつかの例:

入力
{1,2,3}

出力
{{1},{2,3}}
{{2},{1,3}}
{{3},{1,2}}

入力
{1,2,3,4}

出力
{{1},{2,3,4}}
{{2},{1,3,4}}
{{3},{1,2,4}}
{{4},{1,2, 3}}
{{1,2},{3,4}}
{{1,3},{2,4}}
{{1,4},{2,3}}

入力
{1,2,2,3}

出力
{{1},{2,2,3}}
{{2},{1,2,3}}
{{3},{1,2,2}}
{{1,2},{2, 3}}
{{1,3},{2,2}}

私はこれを紙の上で行うことができますが、プログラムで行う簡単な方法を見つけるのに苦労しています. 特定のコード例ではなく、これを行う方法の簡単な疑似コードの説明のみを探しています。

どんな助けでも大歓迎です。ありがとう。

4

4 に答える 4

2

すべてのサブセットを生成していた場合、長さnのリストに対して 2 n 個のサブセットを生成することになります。これを行う一般的な方法は、0 から 2 n -1 までのすべての数値iを反復処理し、 iに設定されているビットを使用して、 i番目のサブセットに含まれる項目を判別することです。これが機能するのは、任意のアイテムが特定のサブセットに存在するか存在しないかのいずれかであるため、nビットのすべての組み合わせを反復することにより、2 n 個のサブセットを反復することになります。

たとえば、(1, 2, 3) のサブセットを生成するには、0 から 7 までの数値を反復処理します。

0 = 000b → () 1 = 001b → (1) 2 = 010b → ( 2 ) 3 = 011b (1, 2) 4 = 100b → (3) 5 = 101b (1, 3 ) ) 6 = 110 b → (2, 3) 7 = 111 b → (1, 2, 3)






問題では、各サブセットとその補数を生成して、相互に排他的なサブセットのペアを取得できます。これを行うと各ペアが繰り返されるため、最大 2 n -1 - 1 まで反復してから停止するだけで済みます。

1 = 001 b → (1) + (2, 3)
2 = 010 b → (2) + (1, 3)
3 = 011 b → (1, 2) + (3)

重複するアイテムを処理するには、リスト アイテムのサブセットではなく、リスト インデックスのサブセットを生成できます。リスト (1, 2, 2, 3) と同様に、代わりにリスト (0, 1, 2, 3) のサブセットを生成し、それらの数値を (1, 2, 2, 3) リストのインデックスとして使用します。基本的に、間接的なレベルを追加します。

これをすべてまとめた Python コードを次に示します。

#!/usr/bin/env python

def split_subsets(items):
    subsets = set()

    for n in xrange(1, 2 ** len(items) / 2):
        # Use ith index if ith bit of n is set.
        l_indices = [i for i in xrange(0, len(items)) if n & (1 << i) != 0]
        # Use the indices NOT present in l_indices.
        r_indices = [i for i in xrange(0, len(items)) if i not in l_indices]

        # Get the items corresponding to the indices above.
        l = tuple(items[i] for i in l_indices)
        r = tuple(items[i] for i in r_indices)

        # Swap l and r if they are reversed.
        if (len(l), l) > (len(r), r):
            l, r = r, l

        subsets.add((l, r))

    # Sort the subset pairs so the left items are in ascending order.
    return sorted(subsets, key = lambda (l, r): (len(l), l))

for l, r in split_subsets([1, 2, 2, 3]):
    print l, r

出力:

(1,) (2, 2, 3)
(2,) (1, 2, 3)
(3,) (1, 2, 2)
(1, 2) (2, 3)
(1, 3) (2, 2)
于 2009-10-05T18:43:30.753 に答える
1

次のC++関数は必要なことを正確に実行しますが、順序は例の順序とは異なります。

// input contains all input number with duplicates allowed
void generate(std::vector<int> input) {
  typedef std::map<int,int> Map;
  std::map<int,int> mp;
  for (size_t i = 0; i < input.size(); ++i) {
    mp[input[i]]++;
  }

  std::vector<int> numbers;
  std::vector<int> mult;
  for (Map::iterator it = mp.begin(); it != mp.end(); ++it) {
    numbers.push_back(it->first);
    mult.push_back(it->second);
  }

  std::vector<int> cur(mult.size());
  for (;;) {
    size_t i = 0;
    while (i < cur.size() && cur[i] == mult[i]) cur[i++] = 0;
    if (i == cur.size()) break;
    cur[i]++;
    std::vector<int> list1, list2;
    for (size_t i = 0; i < cur.size(); ++i) {
      list1.insert(list1.end(), cur[i], numbers[i]);
      list2.insert(list2.end(), mult[i] - cur[i], numbers[i]);
    }
    if (list1.size() == 0 || list2.size() == 0) continue;
    if (list1 > list2) continue;
    std::cout << "{{";
    for (size_t i = 0; i < list1.size(); ++i) {
      if (i > 0) std::cout << ",";
      std::cout << list1[i];
    }
    std::cout << "},{";
    for (size_t i = 0; i < list2.size(); ++i) {
      if (i > 0) std::cout << ",";
      std::cout << list2[i];
    }
    std::cout << "}\n";
  }
}
于 2009-10-05T19:15:43.653 に答える
1

ちょっとした Erlang コードです。問題は、要素が重複している場合に重複が生成されることです。そのため、結果リストをフィルタリングする必要があります...

do([E,F]) -> [{[E], [F]}];
do([H|T]) -> lists:flatten([{[H], T}] ++
                           [[{[H|L1],L2},{L1, [H|L2]}]  || {L1,L2} <- all(T)]).

filtered(L) ->
  lists:usort([case length(L1) < length(L2) of true -> {L1,L2};
                                               false -> {L2,L1} end
              || {L1,L2} <- do(L)]).

擬似コードでは、これは次のことを意味します。

  • 2 つの長いリスト {E,F} の場合、結果は {{E},{F}} になります。
  • より長いリストの場合、最初の要素 H と残りのリスト T を取得して戻ります
    • {{H},{T}} (単一要素リストとしての最初の要素、および残りのリスト)
    • また、T に対してアルゴリズムを再帰的に実行し、結果のリストの {L1,L2} 要素ごとに {{H,L1},{L2}} と {{L1},{H,L2}} を返します。
于 2009-10-05T18:59:40.690 に答える
0

私の提案は...

まず、おそらくハッシュテーブルにある各値の数を数えます。次に、考慮する組み合わせの総数 (カウントの積) を計算します。

その数の組み合わせを繰り返します。

各組み合わせで、ループ カウントを (x として) コピーし、ハッシュテーブル アイテムを介して内部ループを開始します。

ハッシュテーブル項目ごとに、最初のリストのハッシュテーブル キーのインスタンス数として (x modulo count) を使用します。内側のループを繰り返す前に、x をカウントで割ります。

組み合わせの数が整数型をオーバーフローするのではないかと心配している場合、問題は回避できます。ゼロから始まる各項目 (ハッシュマップ キーごとに 1 つ) を持つ配列を使用し、各配列項目を数字として扱う組み合わせを「カウント」します (したがって、配列全体が組み合わせ番号を表します)。異なるベース (対応するカウント)。つまり、配列を「インクリメント」するには、最初にアイテム 0 をインクリメントします。オーバーフローする (そのカウントと等しくなる) 場合は、0 に設定し、次の配列アイテムをインクリメントします。配列の最後を超えてオーバーフローが続く場合は、終了するまでオーバーフロー チェックを繰り返します。

sergdev はこの 2 番目のものと非常によく似たアプローチを使用していると思いますが、ハッシュテーブルではなく std::map を使用しています (std::unordered_map が機能するはずです)。ハッシュテーブルは、多数のアイテムに対して高速である必要がありますが、特定の順序で値を取得することはできません。ただし、キーを追加/削除しない限り、ハッシュテーブル内のキーを通る各ループの順序は一貫している必要があります。

于 2009-10-05T20:16:37.943 に答える