还记得排列组合吗?

  • 在高中的时候最常接触的莫过于排列组合了,毕竟高考必考的嘛。我们先来回忆下这两个的公式是啥:

3e680003579fdd984fd5

  • 排列组合公式

如果看到这个还有一丢丢的印象,说明大家的基础都还不错。那么问题来了,大家都是学计算机的,我们如何用程序去模拟这个过程,从而达到列出所有排列组合的可能呢?

全排列的实现

  • 暴力求解(不可取,不可取)

    相信很多初入门的小伙伴首先想到的就是就是直接通过嵌套多个for循环去遍历不就好了,只要不相等就直接输出,就像下面这样:

    def force(): data = "abc"for i in range(len(data)): for j in range(len(data)): for k in range(len(data)): if data[i] != data[j] and data[j] != data[k] and data[k] != data[i]:print(data[i],data[j],data[k])if __name__ == '__main__':force()/*输出 a b c a c b b a c b c a c a b c b a*/

    看上去还可以的样子,不过这样有几个坏处,如果不想全排列 abc 了,而是想对 abcd 进行全排列了,那么我们必须要修改源代码增加一个for循环,而且如果排列的数很多的话,那这个循环也太多了吧。

  • 递归求解

    上面这种解法实在有点不优雅,那么我们如何在不修改源码的情况下就可以求出所有的排列组合情况呢?我们先来看张图:

  • 解决全排列的重复问题

    细心的小伙伴肯定会发现,上面的代码其实是有问题的,如果排列的数组中有重复的元素那么结果中也会有重复的排列,这是我们不希望看到的。那么我们如何解决这个问题呢?

    要想解决这个问题,我们首先需要知道这个问题是怎么来的,还是参考刚刚的图,我们稍微修改下:

3e68000357a268d39a72

  • 这里写图片描述

  • 对于 abc 的排列,当我们进行排列时,可以先考虑第1位可能有哪些情况,如上图所示有a,b,c三种情况,然后再对后面的两位进行排列,采用相同的思路,所以我们可以很容易的就通过递归实现全排列了。

    def rank(data, step): if len(data) == step+1:print(data) returnelse: for i in range(step, len(data)): data[step], data[i] = data[i], data[step] //让当前首位依次为后面的每一个数rank(data, step + 1) //递归后面的情况data[step], data[i] = data[i], data[step]if __name__ == '__main__': data = list("abc")rank(data, 0)

    运行上面的代码,可以得到和上面暴力求解一毛一样的结果,且这次如果需要对其他情况进行全排列不需要再修改源代码,且只用了一个循环(虽然用递归效率还不如多个循环^-^),不过至少代码看上去还是很优雅的。

3eb20001d52e8fd26277

就拿 cac 这个举个栗子:

当以第一个c为开头时,我们需要对 ac 进行全排列,没问题

当以a为开头时,我们需要对 cc 进行全排列,没问题

当以第二个c为开头时,我们需要对 ca 进行全排列,这就有问题了,ac和ca的全排列不就一样的嘛,而且开头也一样,这个肯定就会有重复了呀,我们对源码稍加修改下:

def is_equal(data,left,right): #判断left到当前right是否有相等的,如果有说明之前已经对这for i in range(left,right): #个进行过全排序了if data[i] == data[right]: return Truereturn Falsedef rank(data, step):if len(data) == step+1:print(data) returnelse: for i in range(step, len(data)): if is_equal(data,step,i): #加一个判断continueelse:data[step], data[i] = data[i], data[step]rank(data, step + 1)data[step], data[i] = data[i], data[step]if __name__ == '__main__':data = list("bcc")rank(data, 0)

ok,这样运行上面的代码的就不会有重复的问题了,这里可能需要小伙伴们多思考下了,不过还是很简单的。

组合问题

  • 问题描述

    加入我有一个数组 [1, 2, 3, 4, 5, 6] ,我想从里面随机选出三个来,问有哪些取法。

  • 解决思路

    同样是递归,因为我们也不知道循环的具体次数嘛,但是要如何递归呢?

3e6b000330615701fe68

  • 这里写图片描述

  • 不要急,假设我们要从上面的数组中选出3个元素出来。

    我们首先从第一个元素下手,对于第一个元素,我们有两个选择:要 or 不要。

    如果要了,那么我们需要选择的元素就少了一个了,我们只需要从后面的元素中选出两个就够了。

    如果不要,我们就从第二个元素继续看,此时我们还是要选出三个(本来想画图演示的,不过这个图好像有点复杂,笔者画图实在是个菜鸟就偷会懒了)。

  • def combine(data, step, select_data, target_num):if len(select_data) == target_num: #选择的元素已经够了,就输出并返回print(select_data) returnif step >= len(data): #没有元素选了而且还没够,也是直接返回returnselect_data.append(data[step]) #选择当前元素combine(data, step + 1, select_data, target_num)select_data.pop() #别忘了从已选择元素中先删除combine(data, step + 1, select_data, target_num) #不选择当前元素if __name__ == '__main__':data = [1, 2, 3, 4, 5, 6]combine(data, 0, [], 3)

    运行上面的代码就可以得出所有的组合可能了,还是比较优雅的。但是同样当数组中有重复元素的时候也会有重复的组合,这个如何解决呢?这个就让小伙伴们自己思考下吧。

总结

  • 排列组合算法还是很贴近我们生活的,在各种算法,项目与面试中也会经常遇见,所以也算程序员必备算法了,上面代码如果有问题也欢迎小伙伴与笔者留言私信交流,一起学习交流一起进步。