您可以这样递归地解决问题:def perm(n, a):
# recursion anchor
if n <= 0:
return [[]]
else:
result = []
for smallPerm in perm(n-1, a):
# take all elements from a
for elem in a:
# and prepend them to all permutations we get from perm(n-1, 1)
result.append([elem] + smallPerm)
return result
您可以将函数编写为两行代码,但我决定将其编写得更详细一些,以便更易于理解。
不知道你对递归有多少了解,但我知道对于初学者来说这并不容易理解。。。让我来解释一下。
在每个递归函数中,都需要一个递归锚点。这是函数不递归而是直接传递结果的点。它通常是定义良好的基本/原子情况。在本例中,n==0。由于只存在一个包含0个元素的置换,即空列表,因此返回一个包含空列表的列表。
我把n<=0放在那里,这样如果一些smartcookie用n=-1调用perm,我们就不会遇到麻烦,但是n<0的例外情况也能解决这个问题。
现在你在这个锚定的基础上,说,好吧,我的函数给出了n==0的正确结果,所以当我的当前n为1时,我可以信任它,我用n-1调用它。现在的诀窍是相信它会为每一个n-1得到正确的结果,不管你从哪里开始!你可能想知道这是怎么可能的。递归函数就是这样,当你编写递归函数时,你已经依赖它们做正确的工作了。
现在这意味着调用perm(n-1, a)会给你所有长度为n-1的排列。接下来要做的就是用a中的所有元素附加(或预先添加)这些排列,这样就有了一个大小为n的排列列表!工作完成了。
但是请注意,有时需要有多个锚定或一个处理多个输入可能性的锚定。
例如著名的低效fibonacci函数:
^{pr2}$
需要两个锚点,因为使用fib(n - 2)可以后退两步!
随访:
为什么这个功能效率低下?原因与Stefan在评论我的第一个解决方案时所指出的相似。
我犯了一个错误,把递归调用放在一个循环中。这不一定是错误的,但在本例中,调用的参数没有被外部循环更改,这使得它不必要。因此,它每次都会重新生成相同的列表,而不是只生成一次然后再保留它。最糟糕的是所有的递归实例都是一样的!
通过观察分支因子,可以看出这有多糟糕。这里的fib函数调用自己两次,这意味着它的分支因子是2。因此,如果您的解决方案需要深入n个步骤,那么您将对该函数进行2^n个计算。
虽然这个分支因子是常量,但我的第一个实现为a中的每个元素调用了自己,因此分支因子等于列表中元素的数量。
我很高兴Stefan没有尝试过perm(20,[1,2,3])这将是3^20的评价,大约是他所尝试的2^20次评价的3000倍。是的,增加了三千倍只是因为多一个元素。这是指数运行时间!
为了简单起见,我忽略了这样一个事实,即问题本身已经按len(a)**n扩展。