Python, Permutation and Combination
排列和组合
排列 P ( n , r ) = A n r = n ! ( n − r ) ! P(n,r)=A_n^r = \frac{n!}{(n-r)!} P(n,r)=Anr=(n−r)!n!
组合 C ( n , r ) = C n r = ( r n ) = n ! r ! ( n − r ) ! C(n,r)=C_n^r=(_r^n)=\frac{n!}{r!(n-r)!} C(n,r)=Cnr=(rn)=r!(n−r)!n!
重复:放回不放回
参考 Math is Fun
例子
[1,2,3,4]
排列无重复 P(4,2)
[[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]]
排列有重复
[[1, 1], [1, 2], [1, 3], [1, 4], [2, 1], [2, 2], [2, 3], [2, 4], [3, 1], [3, 2], [3, 3], [3, 4], [4, 1], [4, 2], [4, 3], [4, 4]]
全排列P(4,4)
[[1, 2, 3, 4], [1, 2, 4, 3], [1, 3, 2, 4], [1, 3, 4, 2], [1, 4, 2, 3], [1, 4, 3, 2], [2, 1, 3, 4], [2, 1, 4, 3], [2, 3, 1, 4], [2, 3, 4, 1], [2, 4, 1, 3], [2, 4, 3, 1], [3, 1, 2, 4], [3, 1, 4, 2], [3, 2, 1, 4], [3, 2, 4, 1], [3, 4, 1, 2], [3, 4, 2, 1], [4, 1, 2, 3], [4, 1, 3, 2], [4, 2, 1, 3], [4, 2, 3, 1], [4, 3, 1, 2], [4, 3, 2, 1]]
组合无重复C(4,2)
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
组合有重复
[[1, 1], [1, 2], [1, 3], [1, 4], [2, 2], [2, 3], [2, 4], [3, 3], [3, 4], [4, 4]]
实现思路
1. 从迭代的角度,对于[1,2,3,4], 先取出一个元素,对余下元素进行取k排列
2. 从搜索回溯的角度,对于[1,2,3,4], 先对1取出,标记,然后对2,3,4, 回溯
代码
# %%
def permutation1(arr, k, combination=False):
'''
arr : 传入数组
k : 选出的元素个数
combination : True代表是组合,默认是排列,有顺序。
'''
def permutation_sub(temp, arr, result, s):
'''
递归方法
temp : 以参数形式存储单个结果
arr : 原始数组
result : 存储所有排列结果
s : 组合时是用到,确保每次递归后的元素不再参与递归。
'''
# 如果结果达到目标数量
if len(temp) == k:
# 添加到总的数组里
result.append(list(temp))
# 返回上一层递归
return
# 默认的是排列
itero = range(len(arr))
# 如果是组合
if combination:
# 需要保证递归过的元素不在参与递归
itero = range(s, len(arr))
# 选中一个元素arr[i]
for i in itero:
# temp+[arr[i]] : 本次递归产生的元素的结果
# arr[:i]+arr[i+1:] : 除去arr[i]后arr所剩下的元素,对剩下元素进行排列
# result : 结果集
# i : 纪录for循环位置
permutation_sub(
temp+[arr[i]], arr[:i]+arr[i+1:], result, i)
res = []
permutation_sub([], arr, res, 0)
return res
def permutation2(arr, k, combination=False, repeat=False):
'''
arr : 传入数组
k : 选出的元素个数
combination : True代表是组合,默认是排列,有顺序。
repeat : 是否重复, 默认不重复
'''
def permutation_sub(temp, arr, used, s, res):
'''
深度优先遍历+回溯
temp : 一次结果存储
arr : 原始数组
used : 记录元素是否被遍历了
s : 排列需要,同上
res : 最终结果集
'''
# 如果 达到目标元素个数,
if len(temp) == k:
# 添加进结果集
res.append(temp[:])
# 结果本次深度搜索
return
# 默认是排列
itero = range(len(arr))
# 如果组合
if combination:
# 需要保证搜索过的元素不在参与搜索
itero = range(s, len(arr))
for i in itero:
# 如果第i元素没有被使用过
if not used[i]:
# 添加进一次结果集
temp.append(arr[i])
# 标记为使用,如果可以重复的话,就标记为一直未使用
used[i] = not repeat
# 递归对后续元素进行搜索
permutation_sub(temp, arr, used, i, res)
# 在最深层pop出元素,进行回溯
temp.pop()
# 回溯后标记为未使用
used[i] = False
# 初始都为未使用.
# 不能用used=[False]*len(arr),习惯不好。
# a=[[1]*3]*2
# a[0][0]=2
# [[2, 1, 1], [2, 1, 1]]
used = [False for _ in range(len(arr))]
res = []
permutation_sub([], arr, used, 0, res)
return res
结果
if __name__ == "__main__":
data = [1, 2, 3, 4]
k = 2
# 结果
print("排列无重复P(4,2)")
print(permutation1(data, k, False))
print(permutation2(data, k, False, False))
print("排列有重复4,2")
print(permutation2(data, k, False, True))
print("组合无重复P(4,2)")
print(permutation1(data, k, True))
print(permutation2(data, k, True, False))
print("组合有重复4,2")
print(permutation2(data, k, True, True))
排列无重复P(4,2)
[[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]]
[[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]]
排列有重复4,2
[[1, 1], [1, 2], [1, 3], [1, 4], [2, 1], [2, 2], [2, 3], [2, 4], [3, 1], [3, 2], [3, 3], [3, 4], [4, 1], [4, 2], [4, 3], [4, 4]]
组合无重复P(4,2)
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
组合有重复4,2
[[1, 1], [1, 2], [1, 3], [1, 4], [2, 2], [2, 3], [2, 4], [3, 3], [3, 4], [4, 4]]
速度
if __name__ == "__main__":
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
k = 4
# jupyter 魔法糖
# 循环执行随机次数,每次运行100个循环
%timeit - n 100 permutation1(data, k, False)
%timeit - n 100 permutation2(data, k, False, False)
def permutation1(arr, k, combination=False):…
6.2 ms ± 56.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
4.71 ms ± 43.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
显然第二个快
1
3
\frac{1}{3}
31