全排列的生成算法

【复制转载】

//全排列的生成算法
//  全排列的生成算法就是对于给定的字符集,用有效的方法将所有可能的全排列无重复无遗漏地枚举出来。任何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......pn
//  1)从排列的右端开始,找出第一个比右边数字小的数字的序号j(j从左端开始计算),即 j=max{i|pi<pi+1}
//  2)在pj的右边的数字中,找出所有比pj大的数中最小的数字pk,即 k=max{i|pi>pj}(右边的数从右至左是递增的,因此k是所有大于pj的数字中序号最大者)
//  3)对换pi,pk
//  4)再将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。
//2.递增进位数制法
//  在递增进位制数法中,从一个排列求另一个排列需要用到中介数。如果用 ki表示排列p1p2...pi...pn中元素pi的右边比pi小的数的个数,则排列的中介数就是对应的
//  排列k1 ...... ki...... kn-1。
//  例如排列839647521的中介数是72642321,7、2、6、......分别是排列中数字8、3、9、......的右边比它小的数字个数。
//  中介数是计算排列的中间环节。已知一个排列,要求下一个排列,首先确定其中介数,一个排列的后继,其中介数是原排列中介数加1,需要注意的是,如果中介数的
//  末位kn-1+1=2,则要向前进位,一般情形,如果ki+1=n-i+1,则要进位,这就是所谓的递增进位制。例如排列839647521的中介数是72642321,则下一个排列的中介数
//  是67342221+1=67342300(因为1+1=2,所以向前进位,2+1=3,又发生进位,所以下一个中介数是67342300)。
//  得到中介数后,可根据它还原对应得排列。
//  算法如下:
//  中介数k1、k2、......、kn-1的各位数字顺序表示排列中的数字n、n-1、......、2在排列中距右端的的空位数,因此,要按k1、k2、......、kn-1的值从右向左
//  确定n、n-1、......、2的位置,并逐个放置在排列中:i放在右起的ki+1位,如果某位已放有数字,则该位置不算在内,最后一个空位放1。
//  因此从67342300可得到排列849617523,它就是839647521的后一个排列。因为9最先放置,k1=6,9放在右起第7位,空出6个空位,然后是放8,k2=7,8放在右起第8位,
//  但9占用一位,故8应放在右起第9位,余类推。
//3.递减进位制数法
//  在递增进位制数法中,中介数的最低位是逢2进1,进位频繁,这是一个缺点。把递增进位制数翻转,就得到递减进位制数。
//  839647521的中介数是67342221(k1k2...kn-1),倒转成为12224376(kn-1...k2k1),这是递减进位制数的中介数:ki(i=n-1,n-2,...,2)位逢i向ki-1位进1。给定排列p,
//  p的下一个排列的中介数定义为p的中介数加1。例如p=839647521,p的中介数为12224376,p的下一个排列的中介数为12224376+1=12224377,由此得到p的下一个
//  排列为893647521。
//  给定中介数,可用与递增进位制数法类似的方法还原出排列。但在递减进位制数中,可以不先计算中介数就直接从一个排列求出下一个排列。具体算法如下:
//  1)如果p(i)=n且i<>n,则p(i)与p(i-1)交换
//  2)如果p(n)=n,则找出一个连续递减序列9、8、......、i,将其从排列左端删除,再以相反顺序加在排列右端,然后将i-1与左边的数字交换
//  例如p=893647521的下一个排列是983647521。求983647521的下一个排列时,因为9在最左边且第2位为8,第3位不是7,所以将8和9从小到大排于最右端364752189,
//  再将7与其左方数字对调得到983647521的下一个排列是367452189。又例如求987635421的下一个排列,只需要将9876从小到大排到最右端并将5与其左方数字3对调,
//  得到534216789。
//4.邻位对换法 效率最高但顺序不自然
//  邻位对换法中下一个排列总是上一个排列某相邻两位对换得到的。以4个元素的排列为例,将最后的元素4逐次与前面的元素交换,可以生成4个新排列:
//  1 2 3 4, 1 2 4 3, 1 4 2 3, 4 1 2 3
//  然后将最后一个排列的末尾的两个元素交换,再逐次将排头的4与其后的元素交换,又生成四个新排列:
//  4 1 3 2, 1 4 3 2, 1 3 4 2, 1 3 2 4
//  再将最后一个排列的开头的两个元素交换,将4从后往前移:
//  3 1 2 4, 3 1 4 2, 3 4 1 2, 4 3 1 2
//  如此循环4!次既可求出全部排列。
//5.元素增值法(n进制法)效率低
//  1)从原始排列p=p1p2......pn开始,第n位加n-1,如果该位的值超过n,则将它除以n,用余数取代该位,并进位(将第n-1位加1)
//  2)再按同样方法处理n-1位,n-2位,......,直至不再发生进位为止,处理完一个排列就产生了一个新的排列
//  3)将其中有相同元素的排列去掉
//  4)当第一个元素的值>n则结束
//  以3个数1、2、3的排列为例:原始排列是1 2 3,从它开始,第3个元素是3,3+2=5,5 Mod 3=2,第2个元素是2,2+1=3,所以新排列是1 3 2。通过元素增值,顺序产生的
//  排列是:1 2 3,1 3 2,2 1 1,2 1 3,2 2 2,2 3 1,2 3 3,3 1 2,3 2 1
//  有下划线的排列中存在重复元素,丢弃,余下的就是全部排列。
//6.递归类算法
//  全排列的生成方法用递归方式描述比较简洁,实现的方法也有多种。
//  1)回溯法
//  回溯法通常是构造一颗生成树。以3个元素为例;树的节点有个数据,可取值是1、2、3。如果某个为0,则表示尚未取值。
//  初始状态是(0,0,0),第1个元素值可以分别挑选1,2,3,因此扩展出3个子结点。用相同方法找出这些结点的第2个元素的可能值,如此反复进行,一旦出现新结点的3个
//  数据全非零,那就找到了一种全排列方案。当尝试了所有可能方案,即获得了问题的解答。
//  2)递归算法
//  如果用P表示n个元素的排列,而Pi表示不包含元素i的排列,(i)Pi表示在排列Pi前加上前缀i的排列,那么,n个元素的排列可递归定义为:
//  如果n=1,则排列P只有一个元素i
//  如果n>1,则排列P由排列(i)Pi构成(i=1、2、....、n-1)。
//  根据定义,容易看出如果已经生成了k-1个元素的排列,那么,k个元素的排列可以在每个k-1个元素的排列Pi前添加元素i而生成。例如2个元素的排列是1 2和2 1,对与个
//  元素而言,p1是2 3和3 2,在每个排列前加上1即生成1 2 3和1 3 2两个新排列,p2和p3则是1 3、3 1和1 2、2 1,按同样方法可生成新排列2 1 3、2 3 1和3 1 2、3 2 1。
//
//  3)循环移位法
//  如果已经生成了k-1个元素的排列,则在每个排列后添加元素k使之成为k个元素的排列,然后将每个排列循环左移(右移),每移动一次就产生一个新的排列。
//  例如2个元素的排列是1 2和2 1。在1 2 后加上3成为新排列1 2 3,将它循环左移可再生成新排列2 3 1、3 1 2,同样2 1 可生成新排列2 1 3、1 3 2和3 2 1。
时间: 2024-10-15 03:08:25

全排列的生成算法的相关文章

【算法系列学习一】全排列的生成算法

上节算法课提到了全排列的生成问题,今天自己在网上查找了一些资料,总结起来有一下几种方法: 一.递归类算法. 二.字典序法. 三.递增进位数制法. 四.递减进位数制法. 五.邻位交换法. 六.n进位制法. 下面一一介绍一下这几种算法. 一.递归类算法. 递归类算法比较简洁,实现的方法也有多种. 1.递归算法(非字典序) 1 #include<iostream> 2 #include<algorithm> 3 4 5 using namespace std; 6 int sum=0;

简单探讨全排列的生成算法

在研究组合数学的时候,常常能够碰见要求生成全排列的情况.下面来简单探讨全排列的递归生成算法. 现有一个序列(1,2,3),将其命名为序列S<a1,a2,a3>, 假定A(a1,a2,a3) 为这个序列的全排列,那么我们可以得到如下若干序列: <a1,A(a2,a3)>  ① <a2,A(a1,a3)>  ② <a3,A(a1,a2)>  ③ 我们再来看①,她还可以展开成如下两个序列: <a1,a2,A(a3)>  ⑤ <a1,a3,A(a2

全排列生成算法

全排列的生成算法, next_permutation_1 可以用于生成多重集的全排列,next_permutation_2不能用于多重集 #include <cstdio> #include <cstring> #include <vector> using namespace std; bool next_permutation_1(vector<int>& vec){ int n = vec.size()-1; for(;n>0;n--){

递归解决全排列生成算法

排列:从n个元素中任取m个元素,并按照一定的顺序进行排列,称为排列: 全排列:当n==m时,称为全排列: 比如:集合{ 1,2,3}的全排列为: { 1 2 3} { 1 3 2 } { 2 1 3 } { 2 3 1 } { 3 2 1 } { 3 1 2 } 方法一: 我们可以将这个排列问题画成图形表示,即排列枚举树,比如下图为{1,2,3}的排列枚举树,此树和我们这里介绍的算法完全一致: 算法思路: (1)n个元素的全排列=(n-1个元素的全排列)+(另一个元素作为前缀): (2)出口:如

[LeetCode] Permutations 排列生成算法之字典序法

字典序排序生成算法 字典序法就是按照字典排序的思想逐一产生所有排列. 例如,由1,2,3,4组成的所有排列,从小到大的依次为: 1234, 1243, 1324, 1342, 1423, 1432, 2134, 2143, 2314, 2341, 2413, 2431, 3124, 3142, 3214, 3241, 3412, 3421, 4123, 4132, 4213, 4231, 4312, 4321. 分析这种过程,看后一个排列与前一个排列之间有什么关系? 再如,设有排列(p)=276

等高线生成算法(转载)

等高线生成算法 输入:离散的采样点坐标和高度值(x_0,y_0,value_0),(x_1,y_1,value_1)......(x_n, y_n, value_n) 输出:等高线图,如下所示 wiki上的Marching squares算法对此有很好的说明,我也是按照wiki上面的步骤来实现这个算法的,下面对该算法的步骤进行简要说明. 输入参数: 1.点的集合(x_0,y_0,value_0),(x_1,y_1,value_1)......(x_n, y_n, value_n) ; 2.高度值

清华版CG 实验2 直线生成算法实现

1.实验目的: 理解基本图形元素光栅化的基本原理,掌握一种基本图形元素光栅化算法,利用OpenGL实现直线光栅化的DDA算法. 2.实验内容: (1) 根据所给的直线光栅化的示范源程序,在计算机上编译运行,输出正确结果: (2) 指出示范程序采用的算法,以此为基础将其改造为中点线算法或Bresenham算法,写入实验报告: (3) 根据示范代码,将其改造为圆的光栅化算法,写入实验报告: (4) 了解和使用OpenGL的生成直线的命令,来验证程序运行结果. 3.实验原理: 示范代码原理参见教材直线

微信红包生成算法 (解)

/** * 微信红包生成算法 * * @param int $total 红包金额 * @param int $num 拆分数量 * @param int $min 拆分的红包最小金额数目 */function set_packet($total, $num, $min = 0.01){ for ($i = 1; $i < $num; $i++) { //随机安全上限 $safe_total = ($total-($num-$i)*$min)/($num-$i); //红包金额 $money =

[迷宫中的算法实践]迷宫生成算法&mdash;&mdash;Prim算法

       普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小.该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现:并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现:1959年,艾兹格·迪科斯彻再次发现了该算法.因此,在某些场合,普里姆