Python 一种迭代而非递归算法,用于找到将n分割为m个部分的所有方法

Python 一种迭代而非递归算法,用于找到将n分割为m个部分的所有方法,python,algorithm,recursion,Python,Algorithm,Recursion,我需要一个函数,它接受两个整数,比如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):

我需要一个函数,它接受两个整数,比如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的方法:

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
下面是@zyzue的基于numpy的函数f()的基准,以供比较:

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

运行split()函数会冻结我的内核,因此无法对其计时。请注意,在it中,split_函数应更改为split,或将函数名称更改为split_函数,以使其正常工作。split_函数可能是更好的选择,以避免与其他名为split的函数混淆。

维护未完成作业的队列

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]]
未完成的作业是一个数字列表,其中第一个元素可能会被进一步拆分。从队列中提取作业,以各种可能的方式拆分第一个元素,并将生成的作业添加回队列

算法如下(折衷伪码)


以下是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))

如果这是一个练习,那么试着实施它。否则,可能就是你想要的。@zyzue这不是练习,我只需要代码。不过,我看不出排列对我有什么帮助。我想不出一个集合,它的所有排列都加上了某个数。排列可以用来生成组合_中缺少的元素,用它产生的元素替换,这些元素的和等于数。例如,组合_与_替换(范围(5),3)生成(0,0,4),但不是(4,0,0)或(0,4,0),它们是它的排列。对,使用
组合_与_替换
更有意义,请参阅我的答案。你的题目和问题不匹配,是n进m还是m进n?
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))