算法-递归-排列组合
Posted 詩和遠方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法-递归-排列组合相关的知识,希望对你有一定的参考价值。
本文主要用递归算法解决大家非常熟悉的排列组合问题。
组合
方法一
需求:求从含m个数据中取出n个数据的所有取法。
解析:设列表为a[m],从m个数取出n个数分为两种情况:
- 取a[0],然后从a[1:m]中取n-1个数
- 不取a[0],然后从a[1:m]中取n个数
以下两种情形可直接输出:
- 当n=1时,每个m的元素各算一种取法
- 当m=n时,所有m元素算一种取法
对应的公式 :
C
m
n
=
C
m
−
1
n
−
1
+
C
m
−
1
n
C_m^n=C_m-1^n-1+C_m-1^n
Cmn=Cm−1n−1+Cm−1n、
C
m
1
=
m
C_m^1=m
Cm1=m、
C
m
m
=
1
C_m^m = 1
Cmm=1
递归关系和可直接解决的子问题已定义清晰,上代码:
def comb_sub(ls, index, max_index, n, working_set, result):
"""get all possible n elements from ls[index:max_index] to result"""
if max_index - index + 1 == n: # comb(n,n) -> output all data
for x in ls[index: max_index + 1]:
working_set.append(x)
result.append(list(working_set))
return
if n == 1: # comb(n,1) -> output data one by one
for x in ls[index: max_index + 1]:
temp_working_set = list(working_set)
temp_working_set.append(x)
result.append(temp_working_set)
return
temp_working_set = list(working_set)
temp_working_set.append(ls[index])
comb_sub(ls, index + 1, max_index, n - 1, temp_working_set, result)
comb_sub(ls, index + 1, max_index, n, working_set, result)
def comb(ls,n):
"""get n items from ls"""
if n > len(ls) or n <= 0:
raise ValueError('n must between 1 and length of ls')
result = []
comb_sub(ls,0,len(ls) - 1,n,[],result)
return result
if __name__ == "__main__":
ls = [1, 2, 3, 4,]
result = comb(ls,3)
for r in result:
print(r)
结果:
[1, 2, 3]
[1, 2, 4]
[1, 3, 4]
[2, 3, 4]
方法二
由于组合是本质是对数据的选择,所以和集合子集问题比较类似,可借用其二进制掩码的实现思路,只是需要保证掩码中有n位是1,实现代码如下:
def subset_mask(length, n):
"""return all possible mask code with n counts 1"""
mask_set = []
num = 2 ** length - 1 # max mask code in decimal mode
while num >= 0:
bin_str = str(bin(num))[2:]
if bin_str.count('1') == n: # count of 1
mask_set.append(bin_str.zfill(length))
num -= 1
return mask_set
def comb(ls, n):
"""generate subsets according to mask codes"""
if n > len(ls) or n <= 0:
raise ValueError('n must between 1 and length of ls')
result = []
mask_set = subset_mask(len(ls), n)
for mask in mask_set:
result.append([j for i,j in zip(mask,ls) if i == '1'])
return result
if __name__ == "__main__":
ls = [1, 2, 3, 4]
result = comb(ls, 3)
for r in result:
print(r)
排列
需求:求从含m个数据中取出n个数据进行排列的所有情况。
实现逻辑方面,可以参考之前介绍过的全排列算法,这里只需加一个参数n用来限制位数即可:
def permutation_sub(ls,start,n,result):
if start == n: # 已选完n个元素,输出
result.append(ls[:start])
return
for i in range(start,len(ls)):
ls[i],ls[start] = ls[start],ls[i] # 后续元素依次与start交换,作为领导元素
permutation_sub(ls,start + 1,n,result)
ls[i],ls[start] = ls[start],ls[i] # 再次交换,复原上次结构,方便进行下个元素的交换
def permutation(ls, n):
if n > len(ls) or n <= 0:
raise ValueError('n must between 1 and length of ls')
perm = []
permutation_sub(ls,0,n,perm)
return perm
if __name__ == "__main__":
ls = [1,2,3,4]
for x in permutation(ls,2):
print(x)
结果:
[1, 2]
[1, 3]
[1, 4]
[2, 1]
[2, 3]
[2, 4]
[3, 2]
[3, 1]
[3, 4]
[4, 2]
[4, 3]
[4, 1]
总结
递归算法可将问题不断拆解为性质相同但规模更小的问题,直到问题足够小能够直接解决。
有些时候递归关系可能不明显,需要调整思考方式,重新设计函数的传参,使其可以分解为相似的子问题。比如组合算法,从ls中取n个数据,comb(ls,n)是非常自然的函数传参方式,但若只有两个参数是不方便进行递归调用的,所以我又设计了一个comb_sub,通过传递多个参数达到方便拆分和递归调用的目的。
以上是关于算法-递归-排列组合的主要内容,如果未能解决你的问题,请参考以下文章