【问题标题】:An iterative rather than recursive algorithm to find all ways to split n into m pieces一种迭代而非递归算法,可找到将 n 拆分为 m 块的所有方法
【发布时间】:2015-09-02 03:44:10
【问题描述】:

我需要一个接受两个整数的函数,比如说 n 和 m,其中 n >= 0 和 m >= 1,它返回一个列表列表,其中包含将 n 拆分为 m 个正整数部分的所有可能方法(顺序很重要,[4, 7, 2] 与 [7, 4, 2] 不同。)
现在,我能够想出一个快速的小递归函数来完成这项工作,如下所示:

def split(number, elements):
    if elements == 1:
        return [[number]]
    ways = []
    for i in range(0, number + 1):
        for j in split_number(number - i, elements - 1):
            values.append([i] + j)
    return values

但是,我可能会使用大量数字,因此需要将其转换为迭代方法。我不确定如何做到这一点,因为它每次“超级调用”都会多次调用自己,这使得甚至难以转换为尾调用和累加器,更不用说使用它们转换为迭代形式了。

示例输出:

split(7, 2) -> [[0, 7], [1, 6], [2, 5], [3, 4], [4, 3], [5, 2], [6, 1], [7, 0]]
split(4, 3) -> [[0, 0, 4], [0, 1, 3], [0, 2, 2], [0, 3, 1], [0, 4, 0], [1, 0, 3], 
[1, 1, 2], [1, 2, 1], [1, 3, 0], [2, 0, 2], [2, 1, 1], [2, 2, 0], [3, 0, 1], [3, 1, 0],
[4, 0, 0]]

等等

【问题讨论】:

  • 如果它是一个练习,然后尝试实施它。否则,itertools.permutations 可能就是您想要的。
  • @zyxue 这不是excersize,我只需要代码。不过,我看不出permutations 对我有什么帮助。我想不出一个集合,它的所有排列都加到某个数字上。
  • 排列可用于生成从combinations_with_replacement 中缺少的元素,这些元素由它产生并且总和为数字。例如,combinations_with_replacement(range(5),3) 生成 (0, 0, 4) 但不生成 (4, 0, 0) 或 (0, 4, 0),它们是它的排列。
  • 对,使用combinations_with_replacement 更有意义,请看我的回答。搞定这件事有点棘手。
  • 您的标题和问题不匹配 - 是 n into m 还是 m into n?

标签: python algorithm recursion


【解决方案1】:

这是使用itertools的一种方式:

def chainsplit(n,p):
    return sorted(list(set(chain.from_iterable([list(permutations(i,len(i))) 
        for i in list(combinations_with_replacement(range(n+1),p)) if sum(i) == n]))))

以下是几个 timeit 基准,显示了列表转换、排序和函数调用的开销:

%timeit set(chain.from_iterable([list(permutations(i,len(i))) for i in list(combinations_with_replacement(range(5),3)) if sum(i) == 4]))
10000 loops, best of 3: 20 µs per loop

%timeit list(set(chain.from_iterable([list(permutations(i,len(i))) for i in list(combinations_with_replacement(range(5),3)) if sum(i) == 4])))
10000 loops, best of 3: 20.4 µs per loop

%timeit sorted(list(set(chain.from_iterable([list(permutations(i,len(i))) for i in list(combinations_with_replacement(range(5),3)) if sum(i) == 4]))))
10000 loops, best of 3: 25 µs per loop

%timeit chainsplit(4,3)
10000 loops, best of 3: 26.4 µs per loop

这里是 @zyxue 的基于 numpy 的函数 f() 的基准进行比较:

timeit f(4,3)
10000 loops, best of 3: 133 µs per loop

运行你的 split() 函数冻结了我的内核,所以我无法计时。请注意,其中 split_function 应更改为 split 或将函数名称更改为 split_function 以使其工作。 split_function 可能是避免与其他名为 split 的函数混淆的更好选择。

【讨论】:

    【解决方案2】:
    import numpy as np
    from itertools import combinations_with_replacement
    
    def f(n, m):
        r = combinations_with_replacement(xrange(n + 1), m - 1)
        return [np.diff([0] + list(x) + [n]).tolist() for x in r]
    
    
    assert f(7, 2) == [[0, 7], [1, 6], [2, 5], [3, 4], [4, 3], [5, 2], [6, 1], [7, 0]]
    assert f(4, 3) == [[0, 0, 4], [0, 1, 3], [0, 2, 2], [0, 3, 1], [0, 4, 0], [1, 0, 3],
                       [1, 1, 2], [1, 2, 1], [1, 3, 0], [2, 0, 2], [2, 1, 1], [2, 2, 0],
                       [3, 0, 1], [3, 1, 0], [4, 0, 0]]
    

    【讨论】:

      【解决方案3】:

      维护未完成作业的队列。

      未完成的工作是数字列表,其中第一个元素可能会进一步拆分。从队列中拉出一个作业,以各种可能的方式拆分第一个元素,然后将生成的作业添加回队列中。

      算法如下(折衷伪代码)

      Q = new Queue
      Q.enqueue ([n])
      While not Q.empty:
         l = Q.dequeue()
         Output.append(l)
         h = l.head
         t = l.tail
         If h != 1:
           For each i in 1 .. h-1:
             Q.enqueue ([i,h-i]++t)
      

      【讨论】:

        【解决方案4】:

        以下是 Python 中的递归转迭代方法:

        def f(n,m):
          stack = [([n] + (m - 1)*[0],0)]
          result = []
          while len(stack) > 0:
            (p,i) = stack.pop()
            if i == m - 1:
              result.append(p)
            else:
              for k in xrange(0,p[i] + 1):
                _p = p[:]
                _p[i] = _p[i] - k
                _p[i + 1] = p[i + 1] + k
                stack.append((_p,i + 1))
          return result
        
        print(f(4,3))
        print(f(7,2))
        

        【讨论】:

          猜你喜欢
          • 2021-07-08
          • 1970-01-01
          • 2016-06-14
          • 2011-11-28
          • 1970-01-01
          • 1970-01-01
          • 2016-07-21
          • 2019-06-12
          • 1970-01-01
          相关资源
          最近更新 更多