我想找到将n
元素分发到b
个分区但没有“重复”和空分箱的所有方法。
示例
如果我有n = 3
个元素和b = 2
个bin并从此stackoverflow线程Bin packing bruteforce method应用bruteforce方法,我会得到以下结果:
[[0, 1, 2, 3], []]
[[0, 1, 2], [3]]
[[0, 1, 3], [2]]
[[0, 1], [2, 3]]
[[0, 2, 3], [1]]
[[0, 2], [1, 3]]
[[0, 3], [1, 2]]
[[0], [1, 2, 3]]
[[1, 2, 3], [0]]
[[1, 2], [0, 3]]
[[1, 3], [0, 2]]
[[1], [0, 2, 3]]
[[2, 3], [0, 1]]
[[2], [0, 1, 3]]
[[3], [0, 1, 2]]
[[], [0, 1, 2, 3]]
“重复”的定义
一半的结果是重复的。只切换垃圾箱的顺序:第一个和最后一个是相同的,第二个和第二个是相同的,等等...
空箱的定义
我不希望任何垃圾箱都是空的。如果你看一下前面的例子,第一行和最后一行都有一个空的bin。
答案 0 :(得分:1)
此类分区的数量称为第二种斯特林数。 这些数字上的Wikipedia article给出了一个递归关系,可以对其进行修改以提供生成这些分区的递归函数。以下Python实现使用memoization来保持计算可行:
def add(a,p,i):
#adds a to the ith cell of partition p
#returns a new partiton
return [piece + [a] if j == i else piece for j, piece in enumerate(p)]
def addToAll(a,p):
#adds a to all pieces of p
#returns a list of partitions
return [add(a,p,i) for i in range(len(p))]
def partition(n,k):
memoDict = {}
def helperPart(n,k):
if n == 0 and k == 0: return [[]]
elif n == 0 or k == 0: return []
elif (n,k) in memoDict:
return memoDict[(n,k)]
else:
kParts = helperPart(n-1,k)
kMinusParts = helperPart(n-1,k-1)
parts = [part + [[n]] for part in kMinusParts]
for p in kParts:
parts.extend(addToAll(n,p))
memoDict[(n,k)] = parts
return parts
return helperPart(n,k)
例如:
>>> partitions = partition(5,3)
>>> for p in partitions: print(p)
[[1, 2, 3], [4], [5]]
[[1, 2, 4], [3], [5]]
[[1, 2], [3, 4], [5]]
[[1, 3, 4], [2], [5]]
[[1, 3], [2, 4], [5]]
[[1, 4], [2, 3], [5]]
[[1], [2, 3, 4], [5]]
[[1, 2, 5], [3], [4]]
[[1, 2], [3, 5], [4]]
[[1, 2], [3], [4, 5]]
[[1, 3, 5], [2], [4]]
[[1, 3], [2, 5], [4]]
[[1, 3], [2], [4, 5]]
[[1, 5], [2, 3], [4]]
[[1], [2, 3, 5], [4]]
[[1], [2, 3], [4, 5]]
[[1, 4, 5], [2], [3]]
[[1, 4], [2, 5], [3]]
[[1, 4], [2], [3, 5]]
[[1, 5], [2, 4], [3]]
[[1], [2, 4, 5], [3]]
[[1], [2, 4], [3, 5]]
[[1, 5], [2], [3, 4]]
[[1], [2, 5], [3, 4]]
[[1], [2], [3, 4, 5]]
效率相当:将10个对象的42,525个分区生成5个分区需要不到一秒的时间。
答案 1 :(得分:0)
似乎我自己找到了解决方案。
我根据this stackoverflow answer调整了Ruby代码以满足我的需求:
class Array
def distribute_to_bins(bins_left)
Enumerator.new do |yielder|
if self.empty?
yielder.yield([])
else
# If there is only one bin left, fill all remaining items in it
min_elements_in_bin = if bins_left == 1
self.size
else
1
end
# Make sure that there are sufficient items left to not get any empty bins
max_elements_in_bin = self.size - (bins_left - 1)
(min_elements_in_bin..max_elements_in_bin).to_a.each do |number_of_elements_in_bin|
self.drop(1).combination(number_of_elements_in_bin - 1).map { |vs| [self.first] + vs }.each do |values|
(self - values).distribute_to_bins(bins_left - 1).each do |group|
yielder.yield([values] + group)
end
end
end
end
end
end
end
执行如下:
pp (1..5).to_a.distribute_to_bins(3).to_a
将产生所有可能性,没有空箱或重复:
[[[1], [2], [3, 4, 5]],
[[1], [2, 3], [4, 5]],
[[1], [2, 4], [3, 5]],
[[1], [2, 5], [3, 4]],
[[1], [2, 3, 4], [5]],
[[1], [2, 3, 5], [4]],
[[1], [2, 4, 5], [3]],
[[1, 2], [3], [4, 5]],
[[1, 2], [3, 4], [5]],
[[1, 2], [3, 5], [4]],
[[1, 3], [2], [4, 5]],
[[1, 3], [2, 4], [5]],
[[1, 3], [2, 5], [4]],
[[1, 4], [2], [3, 5]],
[[1, 4], [2, 3], [5]],
[[1, 4], [2, 5], [3]],
[[1, 5], [2], [3, 4]],
[[1, 5], [2, 3], [4]],
[[1, 5], [2, 4], [3]],
[[1, 2, 3], [4], [5]],
[[1, 2, 4], [3], [5]],
[[1, 2, 5], [3], [4]],
[[1, 3, 4], [2], [5]],
[[1, 3, 5], [2], [4]],
[[1, 4, 5], [2], [3]]]