返回从不同箱中取出的n个对象的所有可能组合的算法

时间:2011-03-14 04:23:15

标签: recursion graph combinations bin

为了使它更具体,我需要一个算法(递归或非递归),给定一个整数n和一个矩阵作为输入,将返回我将拥有的所有组合: 1)每行至少1个对象 2)总共有n个对象

如果我尝试所有组合并使用具有n个对象且每行1个的组合,我觉得我可以更轻松地解决这个问题,但我相信算法可以比这更有效。 我还成功编写了一个算法,该算法将返回每行1个对象的所有组合,但无法将其扩展到更多。我一直用Python编写代码,但任何语言都没问题。考虑到python为每个引用传递对象的额外点。 =)

假设矩阵是平方的。如果有人想知道为什么,这是我想要解决的更复杂的图算法的一部分。

全部谢谢!

4 个答案:

答案 0 :(得分:2)

假设矩阵m是列表列表;这是一个Racket函数:

(define (combinations m n)
  (cond
    ((and (zero? n) (null? m)) '(()))
    ((zero? n) '())
    ((null? m) '())
    ((null? (car m)) '())
    (else
      (append (combinations (cons (cdar m) (cdr m)) n)
              (map (lambda (ls) (cons (caar m) ls))
                   (append
                     (combinations (cons (cdar m) (cdr m)) (sub1 n))
                     (combinations (cdr m) (sub1 n))))))))

答案 1 :(得分:1)

感谢所有的答案,他们接近我所寻找的。我设法在Python下完成它(所以我没有检查这里发布的结果),我的问题实际上是Python在函数调用中传递引用vs复制。我认为浅色副本已经足够了,但显然我需要一份深刻的副本(没想到为什么浅的副本还不够)。

我就这样做了:
PS1:这里的图表是列表的字典。 n_edges是从图中选取的边数 PS2:这里的大小计算非常低效,还没有花时间修复它。
PS3:为了在字典上有序迭代,我创建了两个列表:图表的列表列表(lol)和与之匹配的索引数组(lolindex)。
PS4:适应我发布的问题,我使用的真正方法有更多问题特定的代码。没有按我把它放在这里的方式测试代码。

def pickEdges(n_edges, newgraph):
    size = sum(len(v) for v in newgraph.itervalues())
    if (size == n_edges):
        print newgraph
        return

    for i in range(len(lol)):
        for j in range(len(lol[i])):
                tmpgraph = copy.deepcopy(newgraph)
                if (lol[i][j] not in tmpgraph[lolindex[i]]):
                       tmpgraph[lolindex[i]].append(lol[i][j])
                       pickEdges(n_edges, tmpgraph)

答案 2 :(得分:0)

您很可能想要修改任务并列出简单列表的所有组合? 下面是一个Javascript函数,它将为您执行此操作:

function getWayStr(curr) {
  var nextAbove = -1;
  for (var i = curr + 1; i < waypoints.length; ++i) {
    if (nextAbove == -1) {
      nextAbove = i;
    } else {
      wayStr.push(waypoints[i]);
      wayStr.push(waypoints[curr]);
    }
  }
  if (nextAbove != -1) {
    wayStr.push(waypoints[nextAbove]);
    getWayStr(nextAbove);
    wayStr.push(waypoints[curr]);
  }
 } 

答案 3 :(得分:0)

这是一个很棒的问题!为了与术语保持一致,我将矩阵中的行称为“输入bags”,将输入行中的项称为“对象”。 bag(或multiset)是一个容器,允许成员出现多次但其成员没有固有顺序(与列表不同)。

我们正在寻找具有以下签名的功能:

set of valid combinations =
generate_combinations(list of input bags, number of objects in valid combination)

由于有效组合集可能超出Python可用的内存,generate_combinations应该返回生成器而不是显式列表。

有效结果必须满足以下要求:

  1. 每个输入包中至少有一个对象
  2. 总共有n个对象
  3. 我假设以下内容:

    1. 结果中对象的顺序无关紧要
    2. 输入包可以包含重复的对象
    3. 两个输入袋可以包含重复的物体(在退化的情况下,两个输入袋可以是相同的)
    4. 有效结果无需替换即可拉动对象
    5. 要求#1和假设#4暗示number of input bags <= n <= total number of objects in all bags

      数据结构

      • 由于允许输入行包含重复值(根据假设#2),我们不能使用set / frozenset来存储这些值。 Python列表适用于此任务。另一个容器可以是collections.Counter,它具有恒定时间成员资格测试,并且对于具有许多重复项的列表具有更好的空间效率。
      • 每个有效组合也是一个包
      • 订单与输入袋列表无关,因此可以将其概括为一袋输入袋。为了理智,我会保持原样。

      我将使用Python的内置itertools模块生成组合,这是在v2.6中引入的。如果您运行的是旧版本的Python,请使用配方。对于这些代码示例,为了清楚起见,我已隐式将生成器转换为列表。

      >>> import itertools, collections
      >>> input_bags = [Bag([1,2,2,3,5]), Bag([1,4,5,9]), Bag([12])]
      >>> output_bag_size = 5
      >>> combos = generate_combinations(input_bags, output_bag_size)
      >>> combos.next() #an arbitrary example
      Bag([1,1,2,4,12])
      

      意识到上面提到的问题可以简化为Python内置的itertools模块可以立即解决的问题,该模块生成序列组合。我们需要做的唯一修改是消除要求#1。为了解决减少的问题,将袋子的成员合并成一个袋子。

      >>> all_objects = itertools.chain.from_iterable(input_bags)
      >>> all_objects
      generator that returns [1, 2, 2, 3, 5, 1, 4, 5, 9, 12]
      >>> combos = itertools.combinations(all_objects, output_bag_size)
      >>> combos
      generator that returns [(1,2,2,3,5), (1,2,2,3,1), (1,2,2,3,4), ...]
      

      要恢复要求#1,每个有效组合(输出包)需要包含每个输入包中的1个元素以及来自任何包的其他元素,直到输出包大小达到用户指定的值。如果忽略[来自每个输入包的1个元素]和[来自任何包的附加元素]之间的重叠,则解决方案只是第一个和第二个可能组合的可能组合的笛卡尔积。

      要处理重叠,请从[来自任何行李的附加元素]中删除[每个输入行李中的1个元素]中的元素,并将其转发。

      >>> combos_with_one_element_from_each_bag = itertools.product(*input_bags)
      >>> for base_combo in combos_with_one_element_from_each_bag:
      >>>     all_objects = list(itertools.chain.from_iterable(input_bags))
      >>>     for seen in base_combo:
      >>>         all_objects.remove(seen)
      >>>     all_objects_minus_base_combo = all_objects
      >>>     for remaining_combo in itertools.combinations(all_objects_minus_base_combo, output_bag_size-len(base_combo)):
      >>>         yield itertools.chain(base_combo, remaining_combo)
      

      列表支持删除操作,但生成器不支持。要避免将all_objects作为列表存储在内存中,请创建一个跳过base_combo中元素的函数。

      >>> def remove_elements(iterable, elements_to_remove):
      >>>     elements_to_remove_copy = Bag(elements_to_remove) #create a soft copy
      >>>     for item in iterable:
      >>>         if item not in elements_to_remove_copy:
      >>>             yield item
      >>>         else:
      >>>             elements_to_remove_copy.remove(item)
      

      Bag类的实现可能如下所示:

      >>> class Bag(collections.Counter):
      >>>     def __iter__(self):
      >>>         return self.elements()
      >>>     def remove(self, item):
      >>>         self[item] -= 1
      >>>         if self[item] <= 0:
      >>>             del self[item]
      

      完整代码

      import itertools, collections
      
      class Bag(collections.Counter):
          def __iter__(self):
              return self.elements()
          def remove(self, item):
              self[item] -= 1
              if self[item] <= 0:
                  del self[item]
          def __repr__(self):
              return 'Bag(%s)' % sorted(self.elements())
      
      
      def remove_elements(iterable, elements_to_remove):
          elements_to_remove_copy = Bag(elements_to_remove) #create a soft copy
          for item in iterable:
              if item not in elements_to_remove_copy:
                  yield item
              else:
                  elements_to_remove_copy.remove(item)
      
      def generate_combinations(input_bags, output_bag_size):
          combos_with_one_element_from_each_bag = itertools.product(*input_bags)
          for base_combo in combos_with_one_element_from_each_bag:
              all_objects_minus_base_combo = remove_elements(itertools.chain.from_iterable(input_bags), base_combo)
              for remaining_combo in itertools.combinations(all_objects_minus_base_combo, output_bag_size-len(base_combo)):
                  yield Bag(itertools.chain(base_combo, remaining_combo))
      
      input_bags = [Bag([1,2,2,3,5]), Bag([1,4,5,9]), Bag([12])]
      output_bag_size = 5
      combos = generate_combinations(input_bags, output_bag_size)
      list(combos)
      

      通过添加错误检查代码(例如output_bag_size&gt; = len(input_bags)来完成此操作。

      这种方法的好处是:  1.维护较少的代码(即itertools)  2.没有递归。 Python性能随着调用堆栈高度而降低,因此最小化递归是有益的。  3.最小内存消耗。该算法需要超出输入包列表消耗的空间内存。