全排列算法解析(完整版)
全排列递归算法详解

全排列递归算法详解
哎呀,啥是全排列递归算法呀?我一个小学生刚开始听到这个词的时候,简直是一头雾水,就好像走进了一个黑漆漆的迷宫,找不到出口。
咱们先来说说啥叫排列吧。
比如说,有三个数字1、2、3,把它们排排队,123 是一种排法,213 是一种排法,321 又是一种排法,这就是排列。
那全排列呢,就是把所有可能的排法都找出来。
那递归算法又是啥呢?这就像我们玩的搭积木。
我们先搭一层,然后在这一层的基础上再搭一层,一层一层地往上加。
递归算法也是这样,它自己调用自己,就像搭积木一样,一层一层地解决问题。
比如说,我们要对三个数字1、2、3 进行全排列。
那我们可以先固定第一个数字1,然后对剩下的2 和3 进行全排列。
怎么排呢?再固定2,剩下3 自己就是一种排法;然后固定3,剩下2 又是一种排法。
这是不是有点像我们做数学题的时候,一步一步来,每一步都为下一步打下基础?这不就和我们一层一层搭积木一样嘛!
再想想,如果数字更多了,比如1、2、3、4 这四个数字,那我们还是用同样的方法。
先固定一个数字,然后对剩下的数字进行全排列。
这是不是有点像我们解决难题的时候,把大问题分成一个个小问题,然后一个个解决?
哎呀,说了这么多,我感觉全排列递归算法就像是一个神奇的魔法,能把一堆乱七八糟的数字变得整整齐齐,让我们能找到所有的可能性。
反正我觉得全排列递归算法虽然有点难,但是只要我们认真去想,去琢磨,就一定能搞明白!。
超全超全的排列组合的二十种解法

排列有两种定义,但计算方法只有一种,凡是符合这两种定义的都用这种方法计算。
定义的前提条件是m≦n,m与n均为自然数。
①从n个不同元素中,任取m个元素按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列。
②从n个不同元素中,取出m个元素的所有排列的个数,叫做从n个不同元素中取出m个元素的排列数。
③用具体的例子来理解上面的定义:4种颜色按不同颜色,进行排列,有多少种排列方法,如果是6种颜色呢。
从6种颜色中取出4种进行排列呢。
解:A(4,4)=4x(4-1)x(4-2)x(4-3)x(4-4+1)=4x1x2x3x1=24。
A(6,6)=6x5x4x3x2x1=720。
A(6,4)=6!/(6-4)!=(6x5x4x3x2x1)/2=360。
[计算公式]排列用符号A(n,m)表示,m≦n。
计算公式是:A(n,m)=n(n-1)(n-2)……(n-m+1)=n!/(n-m)!此外规定0!=1,n!表示n(n-1)(n-2) (1)例如:6!=6x5x4x3x2x1=720,4!=4x3x2x1=24。
组合的定义及其计算公式1组合的定义有两种。
定义的前提条件是m≦n。
①从n个不同元素中,任取m个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合。
②从n个不同元素中,取出m个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。
③用例子来理解定义:从4种颜色中,取出2种颜色,能形成多少种组合。
解:C(4,2)=A(4,2)/2!={[4x(4-1)x(4-2)x(4-3)x(4-4+1)]/[2x(2-1)x(2-2+1)]}/[2x(2-1)x(2-2+1)]=[( 4x3x2x1)/2]/2=6。
[计算公式]组合用符号C(n,m)表示,m≦n。
公式是:C(n,m)=A(n,m)/m! 或C(n,m)=C(n,n-m)。
例如:C(5,2)=A(5,2)/[2!x(5-2)!]=(1x2x3x4x5)/[2x(1x2x3)]=10。
全排列问题的解析

1.5全排列的生成算法全排列的生成算法就是对于给定的字符集,用有效的方法将所有可能的全排列无重复无遗漏地枚举出来。
这里介绍全排列算法四种:(A)字典序法(B)递增进位制数法(C)递减进位制数法(D)邻位对换法1.5.1字典序法对给定的字符集中的字符规定了一个先后关系,在此基础上规定两个全排列的先后是从左到右逐个比较对应的字符的先后。
[例]字符集{1,2,3},较小的数字较先,这样按字典序生成的全排列是:123,132,213,231,312,321。
[注意] 一个全排列可看做一个字符串,字符串可有前缀、后缀。
1)生成给定全排列的下一个排列所谓一个的下一个就是这一个与下一个之间没有其他的。
这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。
[例]839647521是1--9的排列。
1—9的排列最前面的是123456789,最后面的是987654321,从右向左扫描若都是增的,就到了987654321,也就没有下一个了。
否则找出第一次出现下降的位置。
/1.5.2递增进位制数法1)由排列求中介数在字典序法中,中介数的各位是由排列数的位决定的.中介数位的下标与排列的位的下标一致。
在递增进位制数法中,中介数的各位是由排列中的数字决定的。
即中介数中各位的下标与排列中的数字(2—n)一致。
可看出n-1位的进位链。
右端位逢2进1,右起第2位逢3进1,…,右起第i位逢i+1进1,i=1,2,…,n-1. 这样的中介数我们称为递增进位制数。
上面是由中介数求排列。
由序号(十进制数)求中介数(递增进位制数)如下:m=m1,0≤m≤n!-1m1=2m2+kn-1,0≤kn-1≤1m2=3m3+kn-2,0≤kn-2≤2……………mn-2=(n-1)mn-1+k2,0≤k2≤n-2mn-1=k1,0≤k1≤n-1p1p2…pn←→(k1k2…kn-1)↑←→m在字典序法中由中介数求排列比较麻烦,我们可以通过另外定义递增进位制数加以改进。
排列组合求法

排列组合求法那咱就开始说说排列组合的求法哈。
一、排列(Permutation)1. 全排列。
假如你有n个不同的东西,要把它们全排列,就像你有n个不同颜色的球,想把它们排成一排,那全排列的方法数就是n!(n的阶乘)。
啥叫n的阶乘呢?就是从1一直乘到n。
比如说你有3个球,红、黄、蓝,那全排列的方法数就是3! = 3×2×1 = 6种。
这6种可能就是红黄蓝、红蓝黄、黄红蓝、黄蓝红、蓝红黄、蓝黄红。
2. 部分排列(从n个不同元素中取出m个元素的排列数)这种情况呢,就是你有n个东西,但是你只选m个出来排列(m≤n)。
这时候排列数的求法就是A(n,m)=n(n 1)(n 2)…(n m+1),也可以写成A(n,m)=(n!)/((n m)!)。
比如说你有5个小伙伴,你要选3个小伙伴站成一排拍照,那排列的方法数就是A(5,3)=(5!)/((5 3)!)=(5×4×3×2×1)/(2×1)=5×4×3 = 60种。
二、组合(Combination)1. 从n个不同元素中取出m个元素的组合数。
组合和排列不一样哦,组合只关心选出哪些元素,不关心它们的顺序。
比如说你从一群小伙伴里选几个人去玩游戏,选谁就是谁,不管谁先谁后。
从n个不同元素中取出m个元素的组合数记为C(n,m),它的求法是C(n,m)=(n!)/(m!(n m)!)。
例如,你有4个不同口味的冰淇淋(草莓、巧克力、香草、抹茶),你想选2个口味来吃,那组合数就是C(4,2)=(4!)/(2!(4 2)!)=(4×3×2×1)/((2×1)×(2×1)) = 6种。
这6种组合就是草莓和巧克力、草莓和香草、草莓和抹茶、巧克力和香草、巧克力和抹茶、香草和抹茶。
全排列的生成算法

全排列的生成算法全排列的生成算法就是对于给定的字符集,用有效的方法将所有可能的全排列无重复无遗漏地枚举出来。
任何n 个字符集的排列都可以与1~n的n个数字的排列一一对应,因此在此就以n个数字的排列为例说明排列的生成法。
n个字符的全体排列之间存在一个确定的线性顺序关系。
所有的排列中除最后一个排列外,都有一个后继;除第一个排列外,都有一个前驱。
每个排列的后继都可以从它的前驱经过最少的变化而得到,全排列的生成算法就是从第一个排列开始逐个生成所有的排列的方法。
全排列的生成法通常有以下几种:字典序法递增进位数制法递减进位数制法邻位交换法递归类算法1.字典序法字典序法中,对于数字1、2、3......n的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。
例如对于5个数字的排列12354和12345,排列12345在前,排列12354在后。
按照这样的规定,5个数字的所有的排列中最前面的是12345,最后面的是54321。
字典序算法如下:设P是1~n的一个全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn1)从排列的右端开始,找出第一个比右边数字小的数字的序号j(j从左端开始计算),即j=max{i|pi<pi+1}2)在pj的右边的数字中,找出所有比pj大的数中最小的数字pk,即 k=max{i|pi>pj}(右边的数从右至左是递增的,因此k是所有大于pj的数字中序号最大者)3)对换pi,pk4)再将pj+1......pk-1pkpk+1pn倒转得到排列p’’=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1,这就是排列p的下一个下一个排列。
例如839647521是数字1~9的一个排列。
从它生成下一个排列的步骤如下:自右至左找出排列中第一个比右边数字小的数字4 839647521在该数字后的数字中找出比4大的数中最小的一个5 839647521将5与4交换 839657421将7421倒转 839651247所以839647521的下一个排列是839651247。
一文搞懂全排列、组合、子集问题

⼀⽂搞懂全排列、组合、⼦集问题微信搜⼀搜:【bigsai】获取更多肝货知识春风⼗⾥,感谢有你前⾔Hello,⼤家好,我是bigsai,long time no see!在刷题和⾯试过程中,我们经常遇到⼀些排列组合类的问题,⽽全排列、组合、⼦集等问题更是⾮常经典问题。
本篇⽂章就带你彻底搞懂全排列!求全排列?全排列即:n个元素取n个元素(所有元素)的所有排列组合情况。
求组合?组合即:n个元素取m个元素的所有组合情况(⾮排列)。
求⼦集?⼦集即:n个元素的所有⼦集(所有可能的组合情况)。
总的来说全排列数值个数是所有元素,不同的是排列顺序;⽽组合是选取固定个数的组合情况(不看排列);⼦集是对组合拓展,所有可能的组合情况(同不考虑排列)。
当然,这三种问题,有相似之处⼜略有所不同,我们接触到的全排列可能更多,所以你可以把组合和⼦集问题认为是全排列的拓展变形。
且问题可能会遇到待处理字符是否有重复的情况。
采取不同的策略去去重也是相当关键和重要的!在各个问题的具体求解上⽅法可能不少,在全排列上最流⾏的就是邻⾥互换法和回溯法,⽽其他的组合和⼦集问题是经典回溯问题。
⽽本篇最重要和基础的就是要掌握这两种⽅法实现的⽆重复全排列,其他的都是基于这个进⾏变换和拓展。
全排列问题全排列,元素总数为最⼤,不同是排列的顺序。
⽆重复序列的全排列这个问题刚好在是原题的,⼤家学完可以去a试试。
问题描述:给定⼀个没有重复数字的序列,返回其所有可能的全排列。
⽰例:输⼊: [1,2,3]输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]回溯法实现⽆重复全排列回溯算法⽤来解决搜索问题,⽽全排列刚好也是⼀种搜索问题,先回顾⼀下什么是回溯算法:回溯算法实际上⼀个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满⾜求解条件时,就“回溯”返回,尝试别的路径.⽽全排列刚好可以使⽤试探的⽅法去枚举所有中可能性。
全排列算法思路解析

全排列算法思路解析
全排列算法是一种基础的算法,用于对给定的一组数据进行全排列。
在程序设计中,全排列算法常常被运用于组合、排序等场景,是一种十分常见的算法。
算法流程如下:
1.设将要排列的元素存在一个字符串S中;
2.将S中的每个字符依次与它后面的字符交换;
3.当S中只剩下一个字符时,输出S;
5.当排列到最后一个元素时,依次输出字符串S的每一个字符,得到一个新的排列。
在算法流程的执行过程中,我们必须清楚的是,每一次交换操作都会对S字符串进行修改。
此外,我们还需要对S字符串的长度和当前元素的位置进行追踪和控制,保证每一个元素都能够交换到相应的位置上。
全排列算法的时间复杂度很高,是O(n!),所以在实际使用中需要耐心地等待程序的执行结果。
总的来说,全排列算法虽然看似简单,但它将我们的编程思维与编程技巧提高到了一个新的水平。
在日常编程的实践中,我们将许多的算法融入到自己的程序中,体现出了我们的编程思维严谨、技巧娴熟,是一种十分有意义的学习与实践过程。
组合数学中的全排列生成算法完整版

组合数学全排列生成算法组合数学中的全排列深成算法历来是组合数学考试的重要考察点,因此在这里我简单的介绍一下6种全排列生成算法的详细过程,并借此比较它们之间的优劣之处。
不论是哪种全排列生成算法,都遵循着“原排列”→“原中介数”→“新中介数”→“新排列”的过程。
其中中介数依据算法的不同会的到递增进位制数和递减进位制数。
关于排列和中介数的一一对应性的证明我们不做讨论,这里仅仅给出了排列和中介数的详细映射方法。
相信熟练掌握了方法就可以顺利通过这部分的考察。
递增进位制和递减进位制数所谓递增进位制和递减进位制数字是指数字的进制随着数字位置的不同递增或递减。
通常我们见到的都是固定进制数字,如2进制,10进制等。
m位n进制数可以表示的数字是m*n个。
而m位递增或递减进位制数则可以表示数字m!个。
例如递增进位制数4121,它的进制从右向左依次是2、3、4、5。
即其最高位(就是数字4那位)最大值可能是4;第三高位最大可能是3;第二高位最大可能是2;最末位最大可能是1。
如果将4121加上1的话,会使最末位得到0,同时进位;第二位的2与进位相加,也会得到0,同时进位;第三位的1与进位相加得到2,不再进位。
最终得到结果是4200。
递减进位制的道理是一样的,只不过进制从右向左依次是9、8、7、6……,正好与递增进位制相反。
很明显,递减进位制的一个最大的好处就是加法不易进位,因为它在进行加法最频繁的末几位里(最右边)进制比较大。
接下来要了解的是递增进位制、递减进位制数和其序号的关系。
递增、递减进位制数可以被看作一个有序的数字集合。
如果规定递增进位制和递减进位制数的0的序号是十进制0,递增进位制数的987654321和递减进位制数的123456789对应十进制序号362880(即9!),则可以整理一套对应法则。
其中,递增进位制数(a1 a2 a3 a4 a5 a6 a7 a8 a9)为:a1*9! + a2*8! + ….+ a8*2! + a9*1! =序号例如序号100的递增进位制数就是4020,即4*4!+ 0*3!+ 2*2!+ 0*1!=100。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1.全排列的定义和公式:
从n个数中选取m(m<=n)个数按照一定的顺序进行排成一个列,叫作从n个元素中取m个元素的一个排列。由排列的定义,显然不同的顺序是一个不同的排列。从n个元素中取m个元素的所有排列的个数,称为排列数。从n个元素取出n个元素的一个排列,称为一个全排列。全排列的排列数公式为n!,通过乘法原理可以得到。
6.全排列的字典序:
字典序的英语一般叫做dictionary order,浅显明白。
定义:对于一个序列a1,a2,a3,a4,a5....an的两个排列b1,b2,b3,b4,b5...bn和c1,c2,c3,c4,,
如果它们的前k(常数)项一样,且ck > bk,则称排列c位于排列b(关于字典序)的后面。如1,2,3,4的字典序排在1,2,4,3的前面(k=2),1,3,2,4的字典序在1,2,3,4(k=1)的后面。下面列出1,2,3按字典序的排列结果:
接下来讲讲如何求某一个排列的紧邻着的后一个字典序。对证明不感兴趣的读者只要读下面加色的字即可。
定理:我们先来构造这样一个在它后面的字典序,再证明这是紧邻它的字典序。对于一个排列a1,a2,a3...an,如果a(n)> a(n-1),那么a1,a2,a3...a(n),a(n-1)是它后面的字典序,否则,也就是a(n-1) > a(n),此时如果a(n-2) < a(n-1),那么在a(n-1)和a(n)中选择比a(n-2)大的较小的那个数,和a(n-2)交换,显然,它也是原排列后面的字典序。更一般地,从a(n)开始不断向前找,直到找到a(m+1) > a(m)【如果a(n)<a(n-1),则找a(n-1)和a(n-2),不断迭代,直到找到这样一组数或者m=1还不满足,则有a(1) > a(2) > ...a(n),是最大的字典序】,显然后面的序列满足a(m+1)>a(m+2)>...a(n).找到a(m+1)到a(n)中比a(m)大的最小的数,和a(m)交换,并把交换后的a(m+1)到a(n)按照从小到大排序,前m-1项保持不变,得到的显然也是原排列后面的字典序,这个字典序便是紧挨着排列的后一个字典序。
全排列以及相关算法
在程序设计过程中,我们往往要对一个序列进行全排列或者对每一个排列进行分析。全排列算法便是用于产生全排列或者逐个构造全排列的方法。当然,全排列算法不仅仅止于全排列,对于普通的排列,或者组合的问题,也可以解决。本文主要通过对全排列以及相关算法的介绍和讲解、分析,让读者更好地了解这一方面的知识,主要涉及到的语言是C和C++。
O(n*n-1*n-2*...1) = O(n!).
对上述算法进行封装,便可以得到列出全排列的函数:
5.全排列算法:
voidFull_Array(intA[],intn)
{
Permutation(A, 0,n);
}
如果读者仅仅需要一个全排列的递归算法,那么看到上面就可以了。下面将对全排列的知识进行扩充。
1,2,3
1,3,2
2,1,3
2,3,1
3,1,2
3,2,1
(有些读者会发现它们手写排列的时候也不自觉得遵照着这个规则以妨漏写,对于计算机也一样,如果有这样习惯的读者的话,那它们的实际算法更适合于表达为下面要讲的算法。)
定义字典序的好处在于,排列变得有序了,而我们前面的递归算法的排列是无序的,由同一个序列生成的不同数组(排列)如1,2,3,4和2,3,4,1的输出结果的顺序是不同的,这样便没有统一性,而字典序则可以解决这个问题。很明显地,对于一个元素各不相同的元素集合,它的每个排列的字典序位置都不相同,有先有后。
本文的节数:
1.全排列的定义和公式:
2.时间复杂度:
3.列出全排列的初始思想:
4.从第m个元素到第n个元素的全排列的算法:
5.全排列算法:
6.全排列的字典序:
7.求下一个字典序排列算法:
8.C++ STL库中的next_permutation()函数:(#include<algorithm>)
9.字典序的中介数,由中介数求序号:
......
5.直到递归到第n个元素到第n元素的全排列,递归出口。
6.将改变的数组变回。
......
8.不断地改变第一个元素,直至n次使for循环中止。
为了实现上述过程,我们要先得到从第m个元素到第n个元素的排列的算法:
4.从第m个元素到第n个元素的全排列的算法:
void Permutation(intA[],intm,intn)
swap(a[m],a[i]);//交换,对应第六步
}
}
为了使代码运行更快,Print函数和swap函数直接写成表达式而不是函数(如果是C++的话建议把swap写成内联函数,把Print写成宏)
voidPermutation(intA[],intm,intn)
{
inti,inttemp;
if(m = = n)
{
for(i = 0;i<n;i+printf("%d ",A[i]); //有加空格
else
printf("%d" A[i]); //没加空格
}//直接输出,因为前n-1个数已经确定,递归到只有1个数。
printf("\n");
return;
}
else
{
for(i=m;i<n;i++)/*进入for循环,对应第一步,注意此处是m,而不是0,因为是递归调用,对应的是第m个元素到第n个元素的全排列。*/
1,3,9,5.
此时5,9的情况都写完了,不能以3打头了,得到
1,5,3,9
1,5,9,3
1,9,3,5
1,9,5,3
这样,我们就得到了1开头的所有排列,这是我们一般的排列数生成的过程。再接着是以3、5、9打头,得到全排列。这里还要注意的一点是,对于我们人而言,我们脑子里相当于是储存了一张表示原有数组的表,1,3,5,9,1开头的所有排列完成后,我们选择3开头,3选完了之后,我们选择5开头,而不会再返过来选1,而且知道选到9之后结束,但对于计算机而言,我们得到了3,5,1,9后,可能再次跳到1当中,因为原来数组的顺序它已经不知道了,这样便产生了错误。对于算法的设计,我们也可以维护这样一个数组,它保存了原始的数据,这是一种方法。同时我们还可以再每次交换后再交换回来,变回原来的数组,这样程序在遍历的时候便不会出错。读者可以练习一下这个过程,思考一下你是如何进行全排列的,当然,你的方法可能和我的不太一样。
2.时间复杂度:
n个数(字符、对象)的全排列一共有n!种,所以全排列算法至少时O(n!)的。如果要对全排列进行输出,那么输出的时间要O(n*n!),因为每一个排列都有n个数据。所以实际上,全排列算法对大型的数据是无法处理的,而一般情况下也不会要求我们去遍历一个大型数据的全排列。
3.列出全排列的初始思想:
我们把上面全排列的方法归纳一下,基本上就是:任意选一个数(一般从小到大或者从左到右)打头,对后面的n-1个数进行全排列。聪明的读者应该已经发现,这是一个递归的方法,因为要得到n-1个数的全排列,我们又要先去得到n-2个数的全排列,而出口是只有1个数的全排列,因为它只有1种,为它的本身。写成比较规范的流程:
return false;
m = i;
i++;
for(;i<n;i++)
if(A[i] <= A[m])
{
i--;
break;
}
swap(A[i],A[m]);
sort(A+m+1,A+n);
Print(A);
return true;
}
swap和Print函数读者可以自己写也可以参照我上面的写法,排序我这里直接使用了C++标准库中的sort,读者也可以自己写。有了这个算法后,我们便可以写一个非递归的列出全排列的方法,而且这个方法还带顺序:
解决一个算法问题,我比较习惯于从基本的想法做起,我们先回顾一下我们自己是如何写一组数的全排列的:1,3,5,9(为了方便,下面我都用数进行全排列而不是字符)。
1,3,5,9.(第一个)
首先保持第一个不变,对3,5,9进行全排列。
同样地,我们先保持3不变,对5,9进行全排列。
保持5不变,对9对进行全排列,由于9只有一个,它的排列只有一种:9。接下来5不能以5打头了,5,9相互交换,得到
证明完成后,我们便可以通过上述的构造方法求得一个排列的下一个字典序排列了。
7.求下一个字典序排列算法:
boolNext_Permutation(intA[],intn)
{
inti,m,temp;
for(i = n-2;i >= 0;i--)
{
if(A[i+1] > A[i])