全排列算法

来源:互联网 发布:linux搭建lamp环境 编辑:程序博客网 时间:2024/06/07 06:40

            n个元素的全排列共有n!个排列结果,所以算法复杂度至少不小于O(n!)。解这类问题的一般方法通常有:

1. 深度搜索:因为解空间已知,相当于搜索一个高度为n满n叉树,树上每一条不出现重复元素的路径即为一个合法的排列,路径总数为2**n次

2. 顺序搜索:问题规模已知,如果所有的排列能够映射成一个有序的集合,并且给定任意一个排列,都有方法得到下一个排列,那么就可以搜索所有的解

3. 子问题跟全局问题的关系:如果全局问题的解能够通过子问题的解构造,那么可以从最小规模解不停加大问题规模来求解

1. 深度搜索

          典型的回溯状态树的过程,每个节点都有n个选择,选择n个元素中的任何一个。因为一条路径不能出现重复的元素,所以需要给已选元素打上标签。代码:

n = int(input('enter a number:'))  nums = [x for x in range(1,n+1)]  ls = []  visited = [0]*n  def permu_dfs(nums,n,dep):      if dep >=n:          print(ls)          return      for i in range(n):          if visited[i] == 0:              ls.append(nums[i])              visited[i] = 1              permu_dfs(nums,n,dep + 1)              visited[i] = 0              ls.pop()   

2. 顺序搜索1--映射状态树

       如果把状态树上每条路径映射成一个整数,比如[0, 1] 2个元素的全排列的状态树映射如下:
0 0 --> 0 = 0*2**1 + 0*2**0 = 0
0 1 --> 1 = 0*2**1 + 1*2**0 = 1
1 0 --> 2 = 1*2**1 + 0*2**0 = 2
1 1-->  3 = 1*2**1 + 1*2**0 = 3
其中,红色的是正确的排列(没有重复的元素),再如[0, 1, 2]3个元素的全排列状态树映射如下:
0 0 0--> 0 = 0*3**2 + 0*3**1 + 0*3**0 = 0
0 0 1--> 1 = 0*3**2 + 0*3**1 + 1*3**0 = 1
0 0 2--> 2 = 0*3**2 + 0*3**1 + 2*3**0 = 2
0 1 0--> 3 = 0*3**2 + 1*3**1 + 0*3**0 = 3
....
0 1 2--> 5 = 0*3**2 + 1*3**1 + 2*3**0 = 5
0 2 0--> 6 = 0*3**2 + 2*3**1 + 2*3**0 = 6
....
2 1 0-->21 = 2*3**2 + 1*3**1 + 0*3**0 = 21
....
2 2 2-->21 = 2*3**2 + 2*3**1 + 2*3**0 = 26
       可以看出,n个元素组成的一个序列(不一定是合法的排列)映射成一个10进制整数,就是把这个排列看成一个n进制的整数,转换公式如下:
d = p[0]*n**(n-1) + p[1]*n**(n-2) + ... + p[n-1]*n**0
       但是没有什么必要做进制转换,比如给定任意一个序列,对其模拟n进制的加1操作,则结果为另外一个序列,比如:2 0 2 + ·1 = 2 1 0
那么算法可以这样描述:
1. 将n个元素排序得到[0,1,2,...,n-1](顺序排列必须从最小序开始,代码假设从最小序开始,省略排序过程)
2. 判断当前序列是否是一个排列(没有重复元素)
3. 对当前序列进行n进制+1操作
4. 重复2-3,直到终止条件:p[0]>=n
n = int(input('enter a number:'))nums = [x for x in range(1,n+1)]def addOne(nums):    i = n - 1    nt = 0    while True:        if i==n-1:            nums[i] = nums[i] + 1 + nt        else:            nums[i] = nums[i]  + nt        if i !=0 and nums[i] > n:            nums[i] = 1            nt = 1            i-=1        else:            breakdef isUnique(nums):    s = set(nums)    return len(s) == len(nums)def permu(nums,n):    while True:        if isUnique(nums):            print(nums)        addOne(nums)        if nums[0] > n:            breakpermu(nums,n)input('pause')

3. 顺序搜索2--康托展开

        上诉方法可以看成f: D--->R的一个映射, 其中D跟R的规模都为n**n,而实际问题的复杂度为 n!,也就是我们为了解问题把问题规模扩大了然后再进行筛选。而康托展开则是n!到n!的一一映射。对于[0, 1, 2]的一个排列1 2 0用康托展开对应的整数计算方法如下:
1.  首先最高位p[0]=1,由于0比1小,所以120前面必定有0xx这样的排列,其个数为1*2!
2.  再看p[1]=2, 第一步已经计算0xx这样的排列,但是还是存在10x这样的排列排除120前面。0跟1都比2小,但是1已经排在了最高位,所以比2小的只有0,则个数为1*1!
3.  最后一位不能考虑。 
得到公式: d = a0*(n-1)! + a1*(n-2)! + an-1*0!,其中ai表示还有p[i+1:n]比p[i]小的元素的个数,所以:
0 1 2 = 0*2! + 0*1! + 0*0! = 0
0 2 1 = 0*2! + 1*1! + 0*0! = 1
1 0 2 = 1*2! + 0*1! + 0*0! = 2
1 2 0 = 1*2! + 1*1! + 0*0! = 3
2 0 1 = 2*2! + 0*1! + 0*0! = 4
2 1 0 = 2*2! + 1*1! + 0*0! = 5
def factorial(n):    facs = [1]*(n+1)    for i in range(1,n+1):        facs[i]=facs[i-1]*i    return facsdef cantorExpand(nums):    n = len(nums)    facs = factorial(n)    r = 0    for i in range(0,n):        #统计比nums[i]小的数的个数        t = 0        for j in range(i+1,n):            if nums[i] > nums[j]:                t+=1        r+=t*facs[n-i-1]    return r
     求全排列刚好跟康托展开相反,所以需要一个函数,对于给定任意一个0到n!-1的整数生成一个全排列,这个过程称为康托逆展开。
在[0,1,2]的一个整数4,映射成一个排列方法如下:
1. 4/2! = 2,说明有2个数比p[0]小,所以p[0]=2,还剩[0,1]两个数
2. 4%2!=0, 0/1!= 0, 说明p[1]要在[0,1]中选择一个数并且[0,1]没有其他数比它小,所以p[1]=0, 剩下[1
3. p[2]=1,所以结果为201
def cantorInv(n, seq, facs):    l=[]    vst=[0]*n    i = n-1    while(i>=0):        t = seq // facs[i] #有t个数比l[n-i-1]小        count = -1 #要比t多一个,所以设为-1        for j in range(n):            if vst[j] == 0:                count+=1            if t==count:                break        l.append(j)        vst[j]=1        seq%=facs[i]        i-=1    return ldef permu_cantor(n):    facs = factorial(n)    l=1    for i in range(1,n+1):        l*=i    for i in range(l):        print(cantorInv(n,i,facs))

4. 顺序搜索3--字典序--交换元素

      利用字典序,将每个排列看成一个序数,如123的排列有123<132<213<231<312<321。那么只要知道了一个最小的排列(利用排序)就可以得出所有的排列结果。给定一个排列132,通过交换元素为主得到它的下一个排列的过程:
1. 如果从高位开始交换,则会漏掉排列。比如123,1跟2交换漏掉了132.所以需要从低位开始交换。
2. 对于132,从后面开始扫描,发现2比3小,交换了只会变小,所以继续往前扫描,发现1比3小。则说明1这个数可以被交换到后面。那么在1后面有2个数可以选择[3,2]。很显然需要一个比1大的中数中最小的一个,所以是2。交换后结果为231
3. 发现交换后漏掉了213,原因是
      1.  假设原来序列为p0p1...pipi+1....pj.....pn-1,设pi是交换点,则p[n-1,..,i+1]是一个递增序列并且pi+1>pi,设pi需要跟pj交换,则pj>pi并且pi>p[j+1,..,n-1]中的任何数,则交换后结果p0p1...pj, pi+1....pi.....pn-1。可以得到p[n-1,...i,...pi+1]是一个递增序列,如果把p[n-1,...i,...pi+1]反转则可得到想要的排列
4. 31反转,得结果213。
n = int(input('enter a number:'))  nums = [x for x in range(1,n+1)]    def findPmax(nums, p, n):      pMax = n - 1      while pMax > p:          if nums[pMax] > nums[p]:              break          pMax = pMax -1      return pMax            def permu_ord(nums,n):      s,p = 0,n-1      print(nums)      while s != p:          q = p          p=p-1          if nums[p] < nums[q]:              pMax = findPmax(nums,p,n)              nums[p],nums[pMax] = nums[pMax],nums[p]              nums[q:] = nums[-1:q-1:-1]              print(nums)              p = n-1            permu_ord(nums,n)input('pause')

5. 从子问题构造

    利用公式:p{123}=1p{23} + 2p{13}+3p{23},其中p{23}=2p{3}+3p{2}=23+32。
n = int(input('enter a number:'))  nums = [x for x in range(1,n+1)]  def permu_swap(nums,n,dep):      if dep==n:          print(nums)      else:          for i in range(dep,n):              nums[dep],nums[i]=nums[i],nums[dep]              permu_swap(nums,n,dep+1)              nums[dep],nums[i]=nums[i],nums[dep] 





















0 0