全排列算法
来源:互联网 发布: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
- 排列与全排列算法
- 全排列算法
- 全排列算法总结
- 全排列算法
- 全排列javascript算法
- [self] 全排列算法
- C# 全排列算法
- 全排列算法
- 全排列算法大全
- 全排列算法
- 全排列递归算法
- 全排列算法设计
- 全排列算法
- 全排列算法
- 全排列算法
- 一个全排列算法
- 全排列算法
- java 全排列算法
- 大数据技术全解:基础、设计、开发与实践
- 找符合条件的整数
- Linux下消息队列和socket绝对速度比拼[转]
- iOS开发: cocoapods的安装与使用
- C语言接口与实现创建可重用软件的技术读书笔记(1)
- 全排列算法
- Servlet和Jsp 设置编码格式
- SSH框架整合后,请求数次页面成功后无法正常跳转,显示等待localhost响应,并不报错。出现的原因
- 模板方法模式--介绍、应用及代码
- unity3d开发2d游戏中控制摄像机移动以及主角移动
- 网友爆料,在佳木斯市发生一起
- 在嵌入式Linux系统上安装打印机
- hdu3652
- ora-01102解决办法