排列和组合是数学里大家都很熟悉的概念,如果问从5个数中挑出3个数的排列有多少种,答案是P(5, 3) = 5 * 4 * 3,如果问从5个数中挑出3个数的组合有多少种,答案是C(5, 3) = P(5, 3)/3! = 5 * 4 * 3 / 1 * 2 * 3,但如果要求把这些排列组合的情形都列出来,那该如何呢?
把这个问题进一步普遍化,列出从n个数中挑出m个数的所有排列/组合,该如何实现?
先看排列的情形。
从经验出发,挑m个数,自然采用m步,每一步挑选一个数,挑够了m个数,便输出一个排列。需要注意的是前面步骤挑出的数在后面不能再出现,也就是要记住前面步骤里已经挑选的数,还要注意的是当输出了一个排列,要计算下一个排列的时候,并不是又要从第一个数重新开始挑,而是采用回溯法,从最后一个数开始,为它挑一个新数,如果还可以挑出一个新的数,挑出那个数,前面的数保留不变,输出排列,如果不能再挑出一个新数,则回退到倒数第二个数,为倒数第二个数挑出一个新的数,然后挑最后一个数,输出排列,如果不能为倒数第二个数挑一个新数,则回退到倒数第三个数,以此类推,下面看代码实现。
def arrange(n, m):
if 0 == m:
yield []
return
mark = bytearray([0] * n)
res = list([-1] * m)
step = 0
def choice(val):
ret = -1
for i in range(n):
if 0 == mark[i] and i > val:
mark[i] = 1 #lock the chosen value
ret = i
break
if val != -1:
mark[val] = 0 #release the old value, it was locked previously
return ret
while step > -1:
if step < m - 1:
res[step] = choice(res[step])
if -1 == res[step]:
step -= 1
else:
step += 1
else:
res[step] = choice(res[step])
if -1 == res[step]:
step -= 1
else:
yield res
for i in arrange(5, 3):
print(i)
这是排列的情形,那么组合呢?组合和排列的不同之处在于,在排列里1, 2, 3和2,3,1是两个排列,而在组合里,它们是同一个组合,既然如此,我们可以用1,2,3代表这个组合,也就是我们可以把组合看成正序的排列。输出所有组合的算法和排列如出一辙,只是更简单了,每一步可以挑选的值是有规律可循的,不需要记住前面步骤挑出的值,下面看代码。
def combination(n, m):
if 0 == m:
yield []
return
res = list([0]*m)
step = 0
res[step] = 0
while step > -1:
if step < m -1:
if res[step] <= n - m + step:
res[step + 1] = res[step] + 1
step += 1
else:
res[step - 1] += 1
step -= 1
else:
if res[step] < n:
yield res
res[step] += 1
else:
res[step - 1] += 1
step -= 1
for i in combination(5, 2):
print(i)