我想通过计算机解决以下小巧的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
符号从B
B
A
,我就会说列表x
包含列表n
。
考虑所有2 ^ n个长度为x = n/3
的1和0的列表,并设置2n/3
。我想计算一组长度为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')
的最小列表,涵盖所有这些列表。 David Eisenstat提供了将这个最小集合覆盖问题转换为混合整数编程问题的代码,该问题可以输入到CPLEX(或http://scip.zib.de/这是开源的)。
{{1}}
问题是,如果你设置n = 15,那么它输出的实例对于我能找到的任何解算器来说都太大了。有没有更有效的方法来解决这个问题所以我可以解决n = 15甚至n = 18?
答案 0 :(得分:4)
这并不能解决你的问题(好吧,不够快),但是你没有得到很多想法,而其他人可能会在这里找到一些有用的东西。
这是一个简短的纯Python 3程序,使用回溯搜索和一些贪婪的订购启发式方法。它可以非常快速地解决N = 3,6和9个实例。它也很快找到N = 12的大小为10的封面,但显然需要花费更长的时间来耗尽搜索空间(我已经没时间了,而且它还在运行)。对于N = 15,初始化时间已经很慢。
Bitstrings在这里由纯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个剩余的短字符串。然后,贪婪的“选择一个覆盖最多的元素”策略很快找到 a 覆盖集合,总共有11个元素,此后不久将覆盖10个元素。这恰好是最佳的,但耗费所有其他可能性需要很长时间。
此时,任何覆盖集合达到10个元素的尝试都将被中止(它不可能小于而不是10个元素!)。如果完全天真地完成,则需要尝试添加(对于“全0”和“全1”字符串)剩余254的所有8个元素子集,并且254-选择-8约为3.8e14。远小于1.2e77 - 但仍然太大而不实用。这是一个有趣的练习,可以理解代码如何设法做得更好。提示:它与此问题中的数据有很大关系。
工业强度求解器无比复杂和复杂。我对这个简单的小程序在较小的问题实例上的表现感到惊喜!幸运的是。
但是对于N = 15,这种简单的方法是没有希望的。它很快就找到了一个包含18个元素的封面,但至少在几个小时内没有更明显的进展。在内部,它仍然使用包含数百(甚至数千)个元素的needed
个集合,这使solve()
的正文非常昂贵。它仍然需要考虑2 ** 10 - 2 = 1022个短字符串,1022-choose-16约为6e34。即使这个代码加速了一百万,我也不指望它会有明显的帮助。
尝试这很有趣: - )
这个版本在完整的N = 12运行中运行速度至少快6倍,只需在一级之前切断无效搜索。还可以加速初始化,并通过将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)
答案 1 :(得分:-1)
首先考虑一下你是否有6位。你可以扔掉2位。因此,任何模式平衡6-0,5-1或4-2都可以转换为0000或1111.在3-3零一平衡的情况下,任何模式都可以转换为四种情况之一:1000,0001 ,因此,6位的一个可能的最小值是:
0000
0001
0111
1110
1000
1111
现在考虑9位丢弃3。您有以下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种可能的模式。
要找到正确的删除,有各种算法。一种方法是找到当前位集和每个主模式之间的编辑距离。具有最小编辑距离的模式是要转换的模式。此方法使用动态编程。另一种方法是使用一组规则对模式进行树搜索以找到匹配模式。