计算具有特定子集大小的集合分区

时间:2014-03-19 22:26:16

标签: ruby algorithm set combinations combinatorics

给定一个带有 n 元素的集合,我需要找到该集合的所有分区,其中 k 子集的大小几乎相等。

例如,对于具有7个元素和3个子集的集合,我只需要分区,其中有两个子集,每个子​​集有2个元素,一个子集有3个元素。我不希望分区具有包含1,2和4个元素的子集。

换句话说,对于一组7个元素有877 possible partitions,但我只对组成具有2/2/3元素的子集的105(?)分区感兴趣:

Graphical representation of the partitions of a 7-element set where subsets have 2, 2, and 3 elements each.

实际上 n 大约是35,这意味着大约有2.81 * 10 27 分区,“仅”8,338,573,669,964,101 partitions with three subsets。因此,我无法计算所有这些,并悄悄找到我想要的那些。

只计算感兴趣的分区的算法是什么?(不是分区的数量,而是每个分区的每个子集中的实际成员。)

5 个答案:

答案 0 :(得分:4)

这是一个很好的方法,只需要通过保持排序来生成所有可能性,以及快速计算答案数量的方法:

def enum(n, k)
  # Pick smaller_size items from the list, repeat smaller_n times
  # then pick larger_size items from the list, repeat larger_n times.
  smaller_n = n.div k
  larger_times = n % k
  smaller_times = k - larger_times
  larger_n = smaller_n + 1

  return to_enum(:enum, n, k) { calc_size(n, smaller_n, smaller_times, larger_n, larger_times) } unless block_given?

  all = [*1..n]
  # split all into one subset to group with the smaller_n and another
  # to group with the larger_n.
  all.combination(smaller_n * smaller_times).each do |smaller|
    larger = all - smaller
    subdivide(smaller, smaller_n) do |small|
      subdivide(larger, larger_n) do |large|
        yield [*small, *large]
      end
    end
  end
end

# Subdivides elems into groups of n, keeping the elements sorted
# and generating only the sorted such combinations.
def subdivide(elems, n)
  return yield [] if elems.empty?
  # No choice for the first element, because we want to keep things sorted.
  first, *rest = elems
  rest.combination(n - 1).each do |comb|
    remain = rest - comb
    subdivide(remain, n) do |sub|
      yield [[first, *comb], *sub]
    end
  end
end

def calc_size(n, smaller_n, smaller_times, larger_n, larger_times)
  all = [
    smaller_times.times.map do |i|
      Array.new(n - i*smaller_n).combination(smaller_n)
    end,
    larger_times.times.map do |i|
      Array.new(n - smaller_times*smaller_n - i*larger_n).combination(larger_n)
    end
  ]
  # Multiply everything, but divide by the number of symmetries, because
  # we don't want to distinguish (1,2), (3,4), ... from (3,4), (1,2), ...
  all.map do |enums|
    enums.map(&:size).inject(1, :*) / enums.permutation.size
  end.inject(:*)
end

p enum(7, 3).size      # => 105 (instant)
p enum(7, 3).first(5)  # => [[[1, 2], [3, 4], [5, 6, 7]],
                       #     [[1, 3], [2, 4], [5, 6, 7]],
                       #     [[1, 4], [2, 3], [5, 6, 7]],
                       #     [[1, 2], [3, 5], [4, 6, 7]],
                       #     [[1, 3], [2, 5], [4, 6, 7]]]
p enum(7, 3).count     # => 105 (quick)
p enum(35, 3).size     # => 564121960420200 (instant)
p enum(35, 3).first(2) # => [[[1..11], [12..23], [24..35]], 
                       #     [[1..11], [12..22, 24], [23, 25..35]]]
p enum(35, 3).count    # => will take forever, should return 564121960420200

注意:只是为了踢,这也可以通过构建一些枚举器并使用size来懒惰地计算大小,而无需迭代它们。这仅适用于Ruby 2.0+,因为它需要Enumerator#size

增加乐趣:

require 'with_progress'
enum(16, 3).with_progress.count # => enjoy!

答案 1 :(得分:2)

重要观察:在分区大小差异不超过1的情况下,分区大小的多分区是唯一定义的。我们有(n % k)部分大小为ceil(n / k)(k - n % k)个大小为floor(n / k)的分区。

因此,让我们修复分区大小,并枚举所有分区的所有可能子集:

@n = n = 7
@k = k = 3
@used = n.times.map { false }
#  @sizes = [2,2,3]  for n = 7, k = 3
@sizes = (k - n % k).times.map { n / k } + (n % k).times.map { (n + k - 1) / k }
@parts = @sizes.map { |i| [0]*i }
def enumerate_part(i, j, lo, y)
  # i = index of the current part
  # j = index of the current number in the current part
  # lo = lower bound for numbers to chose
  # y = output yielder
  if i == @parts.size
    y << @parts.map(&:dup)
    return
  end
  if j == @sizes[i]
    enumerate_part(i + 1, 0, @sizes[i + 1] == @sizes[i] ? @parts[i][0] : 1, y)
    return
  end
  (lo..@n).each do |x|
    next if @used[x]
    @used[x] = true
    @parts[i][j] = x
    enumerate_part(i, j + 1, x + 1, y)
    @used[x] = false
  end
end

results = Enumerator.new { |y| enumerate_part(0,0,1,y) }
puts results.count

请注意,对于相同大小的分区,我为它们分配增加的最小值以实现唯一性。这可能导致一些回溯(最多2个级别),因此它不是最佳的。我们可能希望避免分配数字,我们已经知道下限对于下一个同样大小的分区来说太高了。那时候有点复杂,所以我保持简单。绝对有可能改善这一点。

它仅使用 O(n)空间,因为我改变全局状态而不是复制东西(是的,我向枚举器提供防御性副本,但如果你处理结果则没有必要立即在每次迭代中)。也许不是最优雅的解决方案,但目标是实现良好的速度。

输出:

105

使用较大的 n (超过20)运行此功能并不好玩。我责怪Ruby,但幸运的是,这个特定的实现在几乎任何其他语言中看起来都非常相似。

更新:为了好玩,我将算法改进为输出大小完全线性(除了潜在的 n 因素,因为保留了剩余数字的集合)。它应该快几倍:

def dfs(i, j, lo, eq, sz, state)
  parts, y, n, k, left = state
  if i == k
    y << parts
    return
  end
  if j == sz
    mid = i+1 == n % k
    neweq, newsz = mid ? [k - i - 1, sz-1] : [eq - 1, sz]
    dfs(i+1, 0, mid ? 1 : parts[i][0], neweq, newsz, state)
    return
  end
  higher = ((j == 0) ? (eq * sz - j - 1) : (sz - j - 1))
  left.drop(higher).each_with_index do |x,idx|
    break if x < lo
    parts[i][j] = x
    left.delete_at(idx + higher)
    dfs(i, j + 1, x + 1, eq, sz, state)
    left.insert(idx + higher, x)
  end
end

def niklas(n, k)
  parts = k.times.map{|i| [0]*(i < n%k ? (n+k-1)/k : n/k) }
  left = n.downto(1).to_a
  results = Enumerator.new { |y|
    dfs(0, 0, 1, (n % k == 0) ? k : n % k, (n + k - 1) / k, [parts, y, n, k, left])
  }
  cnt = results.count
  puts "count = #{cnt} != #{@sz}" unless cnt == @sz
end

答案 2 :(得分:1)

我相信这个解决方案(在基准测试中称为“cary1”)将完成这项工作:

<强>代码

require 'set'

def doit(items, subs, so_far=[], combos=Set.new)
  return combos << (so_far+[items]).to_set if subs.size == 1
  items.combination(subs.first){|c|doit(items-c,subs[1..-1],so_far+[c],combos)}
  combos
end

<强>演示

doit([1,2,3,4,5], [2,3]).to_a.map(&:to_a) # 10 combinations
  #=> [[[1, 2], [3, 4, 5]],   [[1, 3], [2, 4, 5]],   [[1, 4], [2, 3, 5]],
  #    [[1, 5], [2, 3, 4]],   [[2, 3], [1, 4, 5]],   [[2, 4], [1, 3, 5]],
  #    [[2, 5], [1, 3, 4]],   [[3, 4], [1, 2, 5]],   [[3, 5], [1, 2, 4]],
  #    [[4, 5], [1, 2, 3]]] 

doit([1,2,3,4,5], [2,1,2]).to_a.map(&:to_a) # 15 combinations
  #=> [[[1, 2], [3], [4, 5]],   [[1, 2], [4], [3, 5]],   [[1, 2], [5], [3, 4]],
  #    [[1, 3], [2], [4, 5]],   [[1, 3], [4], [2, 5]],   [[1, 3], [5], [2, 4]],
  #    [[1, 4], [2], [3, 5]],   [[1, 4], [3], [2, 5]],   [[1, 4], [5], [2, 3]],
  #    [[1, 5], [2], [3, 4]],   [[1, 5], [3], [2, 4]],   [[1, 5], [4], [2, 3]],
  #    [[2, 3], [1], [4, 5]],   [[2, 4], [1], [3, 5]],   [[2, 5], [1], [3, 4]]] 

doit([1,2,3,4,5,6,7], [2,2,3]).to_a.map(&:to_a) # 105 combinations
  # => [[[1, 2], [3, 4], [5, 6, 7]],   [[1, 2], [3, 5], [4, 6, 7]],
  #     [[1, 2], [3, 6], [4, 5, 7]],   [[1, 2], [3, 7], [4, 5, 6]],
  #     [[1, 2], [4, 5], [3, 6, 7]],   [[1, 2], [4, 6], [3, 5, 7]],
  #     ...
  #     [[4, 5], [6, 7], [1, 2, 3]],   [[4, 6], [5, 7], [1, 2, 3]],
  #     [[4, 7], [5, 6], [1, 2, 3]]] 

说明

  • 我将数组subs视为给定,因为构建subs似乎是一个单独且不是非常困难的问题。
  • 为避免[[1],[2,3],[4]][[4],[2,3],[1]]都包含在结果中,我将这两个转换为集合。尝试将这些添加到一组结果时,只会添加第一个结果。我使用集合是因为​​没有说出给定集合的元素,特别是每个对是否可以与<=>进行比较。根据结果​​的使用方式,将结果保留为一组集可能就足够了。或者,可以很容易地将这组集合转换为数组数组,正如我在“演示”部分中所做的那样。

如果subs中没有doit(),则可以在调用doit之前创建def divide_up(items, k) m, r = items.size.divmod(k) puts "m = #{m}, r = #{r}" doit(items, Array.new(k-r, m) + Array.new(r, m+1)) end

demo

编辑:我做了一个小改动,让方法返回一组集合,认为这些可能直接使用。如果没有,可以很容易地将它们转换为数组数组,就像我在{{1}}部分中所做的那样。

答案 3 :(得分:1)

这是一种避免我的第一个解决方案执行不必要的枚举的方法。这种解决方案被称为“cary2&#39;在基准测试中。

<强>代码

def doit(list, group_sizes)
  @group_sizes = group_sizes
  @combos = []
  @empty_group_filled_by_size = group_sizes.uniq.product([false]).to_h
  recurse(list, group_sizes.map { |sub| [] })
  @combos
end

def recurse(remaining_items_to_assign, assigned_so_far)
  empty_group_filled_by_size = @empty_group_filled_by_size.dup
  assigned_so_far.each_with_index do |group, ndx|
    next if group.size == @group_sizes[ndx] # all positions in group allocated
    if group.empty?
      group_size = @group_sizes[ndx]
      empty_group_filled_by_size[group_size] ? next :
        empty_group_filled_by_size[group_size] = true
    end
    assigned_so_far[ndx] << remaining_items_to_assign.first
    if remaining_items_to_assign.size == 1
      @combos << assigned_so_far.map { |group| group.dup } # deep copy
    else
      recurse(remaining_items_to_assign[1..-1], assigned_so_far)
    end
    assigned_so_far[ndx].pop
  end
end

<强>解释

  • 假设我们有12个项目分配给3个大小为2的箱子(B2a,B2b和B2c)和两个大小为3的箱子(B3a和B3b)。项目按顺序分配到箱柜。

  • 可以将第一个项目分配给2个区域(例如B2a)之一或3个区域之一(例如B3a)。它不能按顺序分配给给定大小的每个空仓,因为这会导致重复计算。对于分配给分档的每个给定项目,数组empty_group_filled_by_size用于跟踪项目是否已分配给特定大小的第一个空分档。

  • 第二项的分配可能性取决于哪个bin项目1已分配给。如果项目1已分配给B2a,则项目2可以分配给B2a,另外两个(仍为空)大小为2的箱子(比如B2b)或者分配到两个(空)大小3箱子中的一个(比如说B3a) )。如果项目1已分配给B3a,则可以将项目2分配给三个2个区域(例如B2a),B3a或B3b中的任何一个。

  • 这些作业一直持续到只剩下一个要分配的项目为止,此时只有一个未装满的分区,并且它只有一个项目空间。在将项目分配保存到数组(@combos)中的bin之后,递归会回溯以考虑其他可能性。

答案 4 :(得分:0)

我认为对迄今为止贡献的三种解决方案进行基准测试可能会很有趣。如果有人想看到其他测试,请告诉我,我会添加它们。

<强> Niklas1

def enumerate_part(i, j, lo, y)
  # i = index of the current part
  # j = index of the current number in the current part
  # lo = lower bound for numbers to chose
  # y = output yielder
  if i == @parts.size
    y << @parts.map(&:dup)
    return
  end
  if j == @sizes[i]
    enumerate_part(i + 1, 0, @sizes[i + 1] == @sizes[i] ? @parts[i][0] : 1, y)
    return
  end
  (lo..@n).each do |x|
    next if @used[x]
    @used[x] = true
    @parts[i][j] = x
    enumerate_part(i, j + 1, x + 1, y)
    @used[x] = false
  end
end

def niklas1(n, k)
  @n, @k = n, k
  @used = n.times.map { false }
  #  @sizes = [2,2,3]  for n = 7, k = 3
  @sizes = (k - n % k).times.map { n / k } + (n % k).times.map { (n + k - 1) / k }
  @parts = @sizes.map { |i| [0]*i }
  results = Enumerator.new { |y| enumerate_part(0,0,1,y) }
  cnt = results.count
  puts "count = #{cnt} != #{@sz}" unless cnt == @sz   
end

<强> Niklas2

def dfs(i, j, lo, eq, sz, state)
  parts, y, n, k, left = state
  if i == k
    y << parts
    return
  end
  if j == sz
    mid = i+1 == n % k
    neweq, newsz = mid ? [k - i - 1, sz-1] : [eq - 1, sz]
    dfs(i+1, 0, mid ? 1 : parts[i][0], neweq, newsz, state)
    return
  end
  higher = ((j == 0) ? (eq * sz - j - 1) : (sz - j - 1))
  left.drop(higher).each_with_index do |x,idx|
    break if x < lo
    parts[i][j] = x
    left.delete_at(idx + higher)
    dfs(i, j + 1, x + 1, eq, sz, state)
    left.insert(idx + higher, x)
  end
end

def niklas2(n, k)
  parts = k.times.map{|i| [0]*(i < n%k ? (n+k-1)/k : n/k) }
  left = n.downto(1).to_a
  results = Enumerator.new { |y|
    dfs(0, 0, 1, (n % k == 0) ? k : n % k, (n + k - 1) / k, [parts, y, n, k, left])
  }
  cnt = results.count
  puts "count = #{cnt} != #{@sz}" unless cnt == @sz
end

<强>马克 - 安德烈

def enum(n, k)
  # Pick smaller_size items from the list, repeat smaller_n times
  # then pick larger_size items from the list, repeat larger_n times.
  smaller_n = n.div k
  larger_times = n % k
  smaller_times = k - larger_times
  larger_n = smaller_n + 1

  return to_enum(:enum, n, k) { calc_size(n, smaller_n, smaller_times, larger_n, larger_times) } unless block_given?

  all = [*1..n]
  # split all into one subset to group with the smaller_n and another
  # to group with the larger_n.
  all.combination(smaller_n * smaller_times).each do |smaller|
    larger = all - smaller
    subdivide(smaller, smaller_n) do |small|
      subdivide(larger, larger_n) do |large|
        yield [*small, *large]
      end
    end
  end
end

# Subdivides elems into groups of n, keeping the elements sorted
# and generating only the sorted such combinations.
def subdivide(elems, n)
  return yield [] if elems.empty?
  # No choice for the first element, because we want to keep things sorted.
  first, *rest = elems
  rest.combination(n - 1).each do |comb|
    remain = rest - comb
    subdivide(remain, n) do |sub|
      yield [[first, *comb], *sub]
    end
  end
end

def calc_size(n, smaller_n, smaller_times, larger_n, larger_times)
  all = [
    smaller_times.times.map do |i|
      Array.new(n - i*smaller_n).combination(smaller_n)
    end,
    larger_times.times.map do |i|
      Array.new(n - smaller_times*smaller_n - i*larger_n).combination(larger_n)
    end
  ]
  # Multiply everything, but divide by the number of symmetries, because
  # we don't want to distinguish (1,2), (3,4), ... from (3,4), (1,2), ...
  all.map do |enums|
    enums.map(&:size).inject(1, :*) / enums.permutation.size
  end.inject(:*)
end

def marc_andre(n, k)
  a = enum(n, k).to_a
  a.size
end

<强> Cary1

require 'set'

def cary1(n, k)
  m, r = n.divmod(k)
  cnt = doit([*1..n], Array.new(k-r, m) + Array.new(r, m+1)).to_a.map(&:to_a).size
  puts "count = #{cnt} != #{@sz}" unless cnt == @sz 
end

def doit(items, subs, so_far=[], combos=Set.new)
  return combos << (so_far+[items]).to_set if subs.size == 1
  items.combination(subs.first){|c|doit(items-c,subs[1..-1],so_far+[c],combos)}
  combos
end

<强> Cary2

def cary2(n, k)
  m, r = n.divmod(k)
  cnt = doit2([*1..n], Array.new(k-r, m) + Array.new(r, m+1)).to_a.map(&:to_a).size
  puts "count = #{cnt} != #{@sz}" unless cnt == @sz 
end

def doit2(list, group_sizes)
  @group_sizes = group_sizes
  @combos = []
  @empty_group_filled_by_size = group_sizes.uniq.product([false]).to_h
  recurse(list, group_sizes.map { |sub| [] })
  @combos
end

def recurse(remaining_items_to_assign, so_far)
  empty_group_filled_by_size = @empty_group_filled_by_size.dup
  so_far.each_with_index do |group, ndx|
    next if group.size == @group_sizes[ndx] # all positions in group allocated
    if group.empty?
      group_size = @group_sizes[ndx]
      empty_group_filled_by_size[group_size] ? next :
        empty_group_filled_by_size[group_size] = true
    end
    so_far[ndx] << remaining_items_to_assign.first
    if remaining_items_to_assign.size == 1
      @combos << so_far.map { |group| group.dup } # deep copy
    else
      recurse(remaining_items_to_assign[1..-1], so_far)
    end
    so_far[ndx].pop
  end
end  

基准代码

require 'benchmark'

def bench_it(n, k, iterations)
  puts "\nn = #{n}, k = #{k}, size = #{@sz = marc_andre(n,k)}\n" 
  Benchmark.bm(%w[Niklas Marc-André Cary].map(&:size).max) do |bm|

    bm.report('Niklas1') do
      iterations.times do
        niklas1(n, k)
      end
    end

    bm.report('Niklas2') do
     iterations.times do
       niklas2(n, k)
     end
    end

    bm.report('Marc-André') do
      iterations.times do
        marc_andre(n, k)
      end
    end

    bm.report('Cary1') do
      iterations.times do
        cary1(n, k)
      end
    end

    bm.report('Cary2') do
      iterations.times do
        cary2(n, k)
      end
    end
  end
end

iterations = 1
bench_it( 7, 3, iterations)
bench_it(10, 2, iterations)
bench_it(10, 3, iterations)
bench_it(10, 4, iterations)
bench_it(13, 2, iterations)
bench_it(13, 3, iterations)
bench_it(13, 4, iterations)
bench_it(13, 5, iterations)
bench_it(16, 2, iterations)
bench_it(16, 3, iterations)
bench_it(16, 4, iterations)
bench_it(18, 2, iterations)
bench_it(18, 3, iterations)
bench_it(20, 2, iterations)

<强>结果

最好在后台播放this clip时阅读结果。

n = 7, k = 3, size = 105
                 user     system      total        real
Niklas1      0.000000   0.000000   0.000000 (  0.001131)
Niklas2      0.000000   0.000000   0.000000 (  0.000595)
Marc-André   0.000000   0.000000   0.000000 (  0.000911)
Cary1        0.000000   0.000000   0.000000 (  0.003241)
Cary2        0.000000   0.000000   0.000000 (  0.000922)

n = 10, k = 2, size = 126
                 user     system      total        real
Niklas1      0.010000   0.000000   0.010000 (  0.004986)
Niklas2      0.000000   0.000000   0.000000 (  0.001031)
Marc-André   0.000000   0.000000   0.000000 (  0.000880)
Cary1        0.000000   0.000000   0.000000 (  0.003850)
Cary2        0.000000   0.000000   0.000000 (  0.001607)

n = 10, k = 3, size = 2100
                 user     system      total        real
Niklas1      0.040000   0.000000   0.040000 (  0.042930)
Niklas2      0.010000   0.000000   0.010000 (  0.012296)
Marc-André   0.020000   0.000000   0.020000 (  0.017632)
Cary1        0.080000   0.000000   0.080000 (  0.081082)
Cary2        0.020000   0.000000   0.020000 (  0.018739)

n = 10, k = 4, size = 6300
                 user     system      total        real
Niklas1      0.090000   0.000000   0.090000 (  0.094029)
Niklas2      0.030000   0.000000   0.030000 (  0.030908)
Marc-André   0.040000   0.000000   0.040000 (  0.038247)
Cary1        0.510000   0.000000   0.510000 (  0.512819)
Cary2        0.060000   0.000000   0.060000 (  0.059061)

n = 13, k = 2, size = 1716
                 user     system      total        real
Niklas1      0.210000   0.000000   0.210000 (  0.219898)
Niklas2      0.020000   0.000000   0.020000 (  0.017828)
Marc-André   0.020000   0.000000   0.020000 (  0.015917)
Cary1        0.030000   0.000000   0.030000 (  0.029272)
Cary2        0.020000   0.000000   0.020000 (  0.022058)

n = 13, k = 3, size = 45045
                 user     system      total        real
Niklas1      1.480000   0.010000   1.490000 (  1.484895)
Niklas2      0.350000   0.000000   0.350000 (  0.343872)
Marc-André   0.470000   0.010000   0.480000 (  0.493646)
Cary1        1.890000   0.010000   1.900000 (  1.895684)
Cary2        0.520000   0.010000   0.530000 (  0.531843)

n = 13, k = 4, size = 200200
                 user     system      total        real
Niklas1      4.160000   0.070000   4.230000 (  4.225072)
Niklas2      1.210000   0.000000   1.210000 (  1.216814)
Marc-André   2.170000   0.030000   2.200000 (  2.203629)
Cary1       29.930000   0.580000  30.510000 ( 30.545276)
Cary2        1.720000   0.040000   1.760000 (  1.757895)

n = 13, k = 5, size = 600600
                 user     system      total        real
Niklas1     12.750000   0.040000  12.790000 ( 12.800083)
Niklas2      3.070000   0.010000   3.080000 (  3.085927)
Marc-André   3.630000   0.010000   3.640000 (  3.637411)
Cary1      191.200000   0.890000 192.090000 (192.319270)
Cary2        5.290000   0.300000   5.590000 (  5.625138)

n = 16, k = 2, size = 6435
                 user     system      total        real
Niklas1      2.120000   0.010000   2.130000 (  2.131065)
Niklas2      0.090000   0.010000   0.100000 (  0.092635)
Marc-André   0.080000   0.000000   0.080000 (  0.076312)
Cary1        0.290000   0.000000   0.290000 (  0.295062)
Cary2        0.070000   0.000000   0.070000 (  0.071995)

n = 16, k = 3, size = 1009008
                 user     system      total        real
Niklas1     62.370000   0.940000  63.310000 ( 63.404404)
Niklas2      8.070000   0.020000   8.090000 (  8.099837)
Marc-André  14.080000   0.330000  14.410000 ( 14.424437)
Cary1       48.930000   0.220000  49.150000 ( 49.227445)
Cary2       13.540000   0.280000  13.820000 ( 13.856880)

n = 16, k = 4, size = 2627625
                 user     system      total        real
Niklas2     22.530000   0.290000  22.820000 ( 23.252738)
Marc-André  35.850000   1.160000  37.010000 ( 37.159520)
Cary2       39.850000   0.860000  40.710000 ( 40.780489)

n = 18, k = 2, size = 24310
                 user     system      total        real
Niklas2      0.280000   0.000000   0.280000 (  0.288286)
Marc-André   0.240000   0.020000   0.260000 (  0.262669)
Cary2        0.240000   0.010000   0.250000 (  0.245008)

n = 18, k = 3, size = 2858856
                 user     system      total        real
Niklas2     37.150000   2.670000  39.820000 ( 48.484321)
Marc-André  68.010000   1.350000  69.360000 ( 70.249403)
Cary2       48.300000   0.840000  49.140000 ( 49.591279)

n = 20, k = 2, size = 92378
                 user     system      total        real
Niklas2      1.260000   0.000000   1.260000 (  1.271094)
Marc-André   1.180000   0.040000   1.220000 (  1.205478)
Cary2        1.210000   0.010000   1.220000 (  1.232024)

<强>解释

我们似乎有一个明显的赢家。恭喜,Marc-André!尼克拉斯,这次没有金牌,但白银也不错。

我会告诉我的代码,让@Niklas和@ Marc-André与他们交谈,因为他们更好地理解他们的代码是如何工作的。当k的值很小(2或3)时,我的代码似乎工作得很好。然而,随着该参数的增加,我的代码花费了越来越多的时间来抛出重复项(例如,当我已经“拥有”[[2,3],[3,4,5],[6,7]时解雇[[6,7,[3,4,5][2,3]]。特别是,请查看{{1}的结果这部分是因为我设计了任意分区的代码,而不仅仅是那些看起来像n = 13, k = 5的代码。我将看一下我是否可以做任何事情来利用这个结构(不会彻底改变我的代码) ),但我对结果并不乐观。

修改

我更新了基准测试,以包含我的第二个解决方案(“cary2”)的结果。它发布了与[m, m,...,m, m+1, m+1,...m+1]大致相当的解决方案时间,考虑到两种算法都避免了不必要的组合枚举,这并不奇怪。

修改2

我已经更新了基准,以包含@Niklas的第二个解决方案。我把新的“Niklas2”和原来的“Niklas2”贴上了标签。我还为@Marc-André添加了两个测试。我尝试运行n = 18(用于“Niklas1”,“Marc-André”和“Cary2”),但是在5分钟左右后取消了它,因为它没有获得初始解决方案。

我相信结果不言而喻。