16

ミニマムセットカバー関連】

次のパズルを n の小さなサイズのコンピュータで解きたいと思います。長さ n のすべての 2^n バイナリ ベクトルを考えます。それぞれについて、正確に n/3 のビットを削除し、2n/3 の長さのバイナリ ベクトルを残します (n は 3 の整数倍であると仮定します)。目標は、最後に残る長さ 2n/3 の異なるバイナリ ベクトルの数を最小限に抑えるように、削除するビットを選択することです。

たとえば、n = 3 の場合、最適な答えは 2 つの異なるベクトル 11 と 00 です。n = 6 の場合は 4、n = 9 の場合は 6、n = 12 の場合は 10 です。

私は以前、この問題を次のような最小集合カバー問題として解決しようと試みました。すべてのリストには 1 と 0 のみが含まれます。

正確に記号を挿入して作成できる場合、リストはリストAをカバーすると言います。BBAx

lengthnと setの 1 と 0 のすべての 2^n リストを考えますx = n/32n/3それらすべてをカバーする長さのリストの最小セットを計算したいと思います。David Eisenstat は、この最小集合被覆問題を CPLEX (またはオープン ソースのhttp://scip.zib.de/ )に供給できる混合整数計画問題に変換するコードを提供しました。

from collections import defaultdict
from itertools import product, combinations

def all_fill(source, num):
    output_len = (len(source) + num)
    for where in combinations(range(output_len), len(source)):
        poss = ([[0, 1]] * output_len)
        for (w, s) in zip(where, source):
            poss[w] = [s]
        for tup in product(*poss):
            (yield tup)

def variable_name(seq):
    return ('x' + ''.join((str(s) for s in seq)))
n = 12
shortn = ((2 * n) // 3)
x = (n // 3)
all_seqs = list(product([0, 1], repeat=shortn))
hit_sets = defaultdict(set)
for seq in all_seqs:
    for fill in all_fill(seq, x):
        hit_sets[fill].add(seq)
print('Minimize')
print(' + '.join((variable_name(seq) for seq in all_seqs)))
print('Subject To')
for (fill, seqs) in hit_sets.items():
    print(' + '.join((variable_name(seq) for seq in seqs)), '>=', 1)
print('Binary')
for seq in all_seqs:
    print(variable_name(seq))
print('End')

問題は、n=15 に設定すると、出力されるインスタンスが、私が見つけたどのソルバーに対しても大きすぎることです。n = 15またはn = 18を解決できるように、この問題を解決するより効率的な方法はありますか?

4

2 に答える 2

4

これで問題が解決するわけではありませんが (十分に速くはありません)、多くのアイデアが得られず、他の誰かがここで構築するのに役立つ何かを見つけるかもしれません。

これは短い純粋な Python 3 プログラムであり、貪欲な順序付けヒューリスティックを使用したバックトラッキング検索を使用しています。N = 3、6、および 9 のインスタンスを非常に迅速に解決します。N=12 のサイズ 10 のカバーもすばやく見つけますが、検索スペースを使い果たすのにはるかに長い時間がかかるようです (これには時間がなく、まだ実行中です)。N=15 の場合、初期化時間は既に遅くなっています。

ここでは、ビット文字列は単純な N ビット整数で表されるため、ストレージをほとんど消費しません。これは、より高速な言語での再コーディングを容易にするためです。整数のセットを多用しますが、他の「高度な」データ構造は使用しません。

これが誰かを助けることを願っています! しかし、N の増加に伴う可能性の組み合わせの爆発により、問題の数学を深く掘り下げることなく、「十分に速い」ものは何もないことが保証されることは明らかです。

def dump(cover):
    for s in sorted(cover):
        print("    {:0{width}b}".format(s, width=I))

def new_best(cover):
    global best_cover, best_size
    assert len(cover) < best_size
    best_size = len(cover)
    best_cover = cover.copy()
    print("N =", N, "new best cover, size", best_size)
    dump(best_cover)

def initialize(N, X, I):
    from itertools import combinations
    # Map a "wide" (length N) bitstring to the set of all
    # "narrow" (length I) bitstrings that generate it.
    w2n = [set() for _ in range(2**N)]
    # Map a narrow bitstring to all the wide bitstrings
    # it generates.
    n2w = [set() for _ in range(2**I)]
    for wide, wset in enumerate(w2n):
        for t in combinations(range(N), X):
            narrow = wide
            for i in reversed(t):  # largest i to smallest
                hi, lo = divmod(narrow, 1 << i)
                narrow = ((hi >> 1) << i) | lo
            wset.add(narrow)
            n2w[narrow].add(wide)
    return w2n, n2w

def solve(needed, cover):
    if len(cover) >= best_size:
        return
    if not needed:
        new_best(cover)
        return
    # Find something needed with minimal generating set.
    _, winner = min((len(w2n[g]), g) for g in needed)
    # And order its generators by how much reduction they make
    # to `needed`.
    for g in sorted(w2n[winner],
                    key=lambda g: len(needed & n2w[g]),
                    reverse=True):
        cover.add(g)
        solve(needed - n2w[g], cover)
        cover.remove(g)

N = 9  # CHANGE THIS TO WHAT YOU WANT

assert N % 3 == 0
X = N // 3 # number of bits to exclude
I = N - X  # number of bits to include

print("initializing")
w2n, n2w = initialize(N, X, I)
best_cover = None
best_size = 2**I + 1  # "infinity"
print("solving")
solve(set(range(2**N)), set())

N=9 の出力例:

initializing
solving
N = 9 new best cover, size 6
    000000
    000111
    001100
    110011
    111000
    111111

ファローアップ

N=12 の場合、これは最終的に終了し、最小被覆セットに 10 個の要素が含まれていることが確認されました (最初にすぐに見つかりました)。時間は計りませんでしたが、少なくとも5時間かかりました。

なぜですか?脳死に近いからです ;-)完全に素朴な検索では、256 個の 8 ビットの短い文字列のすべてのサブセットを試します。そのようなサブセットは 2**256 あり、約 1.2e77 です - 予想される宇宙の存続期間内には終了しません ;-)

ここでの順序付けギミックは、最初に「すべて 0」と「すべて 1」の短い文字列がいずれかのカバー セットに含まれている必要があることを検出するので、それらを選択します。これで、残りの 254 個の短い文字列「のみ」を見ることになります。次に、貪欲な「最も多くをカバーする要素を選択する」戦略により、合計 11 個の要素を含むカバー セットがすぐに見つかり、その後すぐに 10 個の要素を含むカバーが見つかります。これはたまたま最適ですが、他のすべての可能性を使い果たすには長い時間がかかります。

この時点で、10 要素に達するカバー セットの試行はすべて中止されます ( 10 要素未満になることはありません!)。それもまったく素朴に行われた場合、残りの 254 個の 8 要素サブセットすべてを (「すべて 0」および「すべて 1」の文字列に) 追加する必要があり、254-choose-8 は約 3.8e14 です。1.2e77 よりもはるかに小さいですが、それでも大きすぎて実用的ではありません。コードがそれよりもはるかにうまく機能する方法を理解することは、興味深い演習です。ヒント: これは、この問題のデータに大きく関係しています。

工業用強度のソルバーは、比類のないほど高度で複雑です。この単純な小さなプログラムが、小さな問題のインスタンスに対してどれほどうまく機能するかに、私は嬉しい驚きを感じました! ラッキーになりました。

しかし、N=15 の場合、この単純なアプローチは絶望的です。18 要素のカバーをすばやく見つけますが、少なくとも数時間は目に見える進歩がありません。内部的には、まだneeded数百 (場合によっては数千) の要素を含むセットで動作しているため、本体がsolve()非常に高価になります。まだ 2**10 - 2 = 1022 個の短い文字列を考慮する必要があり、1022-choose-16 は約 6e34 です。このコードが 100 万倍高速化されたとしても、目に見えて役立つとは思いません。

でも、やってみるのは楽しかったです:-)

そしてちょっと書き直し

このバージョンは、完全な N=12 実行で少なくとも 6 倍高速に実行されます。無駄な検索を 1 レベル早くカットするだけです。また、初期化を高速化し、2**N 個のセットをリストに変更することでメモリ使用量を削減w2nします (セット操作は使用されません)。ただし、N=15 にはまだ絶望的です :-(

def dump(cover):
    for s in sorted(cover):
        print("    {:0{width}b}".format(s, width=I))

def new_best(cover):
    global best_cover, best_size
    assert len(cover) < best_size
    best_size = len(cover)
    best_cover = cover.copy()
    print("N =", N, "new best cover, size", best_size)
    dump(best_cover)

def initialize(N, X, I):
    from itertools import combinations
    # Map a "wide" (length N) bitstring to the set of all
    # "narrow" (length I) bitstrings that generate it.
    w2n = [set() for _ in range(2**N)]
    # Map a narrow bitstring to all the wide bitstrings
    # it generates.
    n2w = [set() for _ in range(2**I)]
    # mask[i] is a string of i 1-bits
    mask = [2**i - 1 for i in range(N)]
    for t in combinations(range(N), X):
        t = t[::-1]  # largest i to smallest
        for wide, wset in enumerate(w2n):
            narrow = wide
            for i in t:  # delete bit 2**i
                narrow = ((narrow >> (i+1)) << i) | (narrow & mask[i])
            wset.add(narrow)
            n2w[narrow].add(wide)
    # release some space
    for i, s in enumerate(w2n):
        w2n[i] = list(s)
    return w2n, n2w

def solve(needed, cover):
    if not needed:
        if len(cover) < best_size:
            new_best(cover)
        return
    if len(cover) >= best_size - 1:
        # can't possibly be extended to a cover < best_size
        return
    # Find something needed with minimal generating set.
    _, winner = min((len(w2n[g]), g) for g in needed)
    # And order its generators by how much reduction they make
    # to `needed`.
    for g in sorted(w2n[winner],
                    key=lambda g: len(needed & n2w[g]),
                    reverse=True):
        cover.add(g)
        solve(needed - n2w[g], cover)
        cover.remove(g)

N = 9  # CHANGE THIS TO WHAT YOU WANT

assert N % 3 == 0
X = N // 3 # number of bits to exclude
I = N - X  # number of bits to include

print("initializing")
w2n, n2w = initialize(N, X, I)

best_cover = None
best_size = 2**I + 1  # "infinity"
print("solving")
solve(set(range(2**N)), set())

print("best for N =", N, "has size", best_size)
dump(best_cover)
于 2013-10-18T18:08:52.487 に答える
-1

最初に、6 ビットがあるかどうかを検討します。2ビットを捨てることができます。したがって、6-0、5-1、または 4-2 の任意のパターン バランスを 0000 または 1111 に変換できます。 、0111、または 1110 です。したがって、6 ビットの可能な最小セットの 1 つは次のとおりです。

0000
0001
0111
1110
1000
1111

ここで、3 を捨てた 9 ビットを考えてみましょう。次の 14 個のマスター パターンのセットがあります。

000000
100000
000001
010000
000010
110000
000011
001111
111100
101111
111101
011111
111110
111111

言い換えると、各パターン セットの中央には 1/0 があり、各端には n/3-1 ビットのすべての順列があります。たとえば、24 ビットの場合、中央に 17 ビット、両端に 7 ビットになります。2^7 = 128 なので、4 x 128 - 2 = 510 通りのパターンがあります。

正しい削除を見つけるには、さまざまなアルゴリズムがあります。1 つの方法は、現在のビット セットと各マスター パターンの間の編集距離を見つけることです。編集距離が最小のパターンが変換対象になります。この方法は動的計画法を使用します。もう 1 つの方法は、一連のルールを使用してパターンをツリー検索し、一致するパターンを見つけることです。

于 2013-10-11T20:28:02.673 に答える