0.摘要
给定n个不相同的数字,输出所有的排列组合。
1.思路
先看一下排列组合的思路:
step1:a[0],a[1]……a[n],从n个元素选择一个
step2:除去step1已经选取的元素,从n-1个元素再选择一个
……
step(n):除去step1-step(n-1)已经选取的n-1个元素,只剩一个元素可选。
根据排列组合的思路:n个不同的数,排列组合共有n!种。因此,直接的思路是,使用n重for循环遍历,即可得到所有排列组合结果。
但是,这样的思路存在两个问题:
- 问题一:题目中n无法确定,所以for循环层数不确定。上述思路只适合n为确定数字的情况;
- 问题二:如何保证已经选取的元素,不再被选取
2.改进思路
我们发现,在上一种思路中,每一步的操作都是类似的,并且最后终止的条件都是“只剩一个元素”。
这样的情况非常符合运用递归的条件:
- 可以把要解决的问题转化为一个新问题,而这个新的问题的解决方法仍与原来的解决方法相同,只是所处理的对象有规律地递增或递减。
- 可以应用这个转化过程使问题得到解决。
- 必定要有一个明确的结束递归的条件。
因此我们考虑使用递归解决问题:
import numpy as np
def fun(in_list,out_list):
if len(in_list) == 0:
print(out_list)
else:
for element in in_list:
inner_in = in_list.copy()
inner_out = out_list.copy()
inner_in.remove(element)
inner_out.append(element)
fun(inner_in,inner_out)
def full_permutation(in_list):
out_list = []
fun(in_list,out_list)
a = np.arange(5).tolist()
full_permutation(a)
问题一的解决:
在fun()函数中,我们实现了递归操作,并添加了for循环语句。这样,就能够利用递归构建多层for循环。从而解决了问题一
程序最后给出了一个简单的测试样例,读者可自己运行,以求验证。
问题二的解决:
我们只需要把已经用过的数字,从列表中删除,这样就能保证下一次不会再取到。
这里之所以需要对in_list,out_list进行copy操作,是因为在python中,形参和实参会相互影响,具体可看下面的例子:
def fun(lst):
lst.pop()
lst = [1,2,3,4,5,6,7]
fun(lst)
print(lst)
fun(lst)
print(lst)
fun(lst)
print(lst)
fun(lst)
print(lst)
fun(lst)
print(lst)
从运行结果上,可以看出,fun()中的lst和原始的lst是同一个变量。
所以,在递归过程中,需要调用list.copy(),将备份传入函数形参中,否则不同递归路径的结果会相互影响,这是很重要的一点。
3.利用辅助数组解决问题二
在上一步的思路中,我们将列表大量copy,这样防止了不同递归路径的数据干扰,但却需要占用大量空间。如何在不复制列表的情况下,解决问题二呢?
方法当然是有的,引入辅助列表即可解决。引入一个列表,记录数据是否已经被使用。
当该数据已被使用,则状态为False,下一次取数的时候,就不会取到该数据。
import numpy as np
def fun(n,in_list,state,out_list):
if (n == 0):
print(out_list)
else:
for i in range(len(in_list)):
if state[i]:
out_list[n-1] = in_list[i]
state[i] = False
fun(n-1,in_list,state,out_list)
state[i] = True
def full_permutation(in_list):
n = len(in_list)
state = [True for _ in range(n)]
out_list = in_list.copy()
fun(n,in_list,state,out_list)
a = np.arange(4).tolist()
full_permutation(a)
在这段代码中,我们使用了state[]记录排列组合数据的状态。
在递归过程中,数据使用后,状态置为False;
本次递归结束后,数据状态要恢复为True,不影响其他递归过程对数据的使用。
另外,为了方便书写,排列组合取值从最后一位开始。