算法-递归-排列组合

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=Cm1n1+Cm1n 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,通过传递多个参数达到方便拆分和递归调用的目的。

以上是关于算法-递归-排列组合的主要内容,如果未能解决你的问题,请参考以下文章

组合和排列算法(递归)

全排列递归算法

java字母和数字排列组合后

算法之使用递归求解全排列

关于全排的递归算法

《算法竞赛进阶指南》-AcWing-94. 递归实现排列型枚举-题解