全排列算法1

全排列以及相关算法

在程序设计过程中,我们往往要对一个序列进行全排列或者对每一个排列进行分析。全排列算法便是用于产生全排列或者逐个构造全排列的方法。当然,全排列算法不仅仅止于全排列,对于普通的排列,或者组合的问题,也可以解决。本文主要通过对全排列以及相关算法的介绍和讲解、分析,让读者更好地了解这一方面的知识,主要涉及到的语言是C和C 。 本文的节数:

1.全排列的定义和公式:

2.时间复杂度:

3.列出全排列的初始思想:

4.从第m个元素到第n个元素的全排列的算法:

5.全排列算法:

6.全排列的字典序:

7.求下一个字典序排列算法:

8.C STL库中的next_permutation()函数:(#include)

9.字典序的中介数,由中介数求序号:

10.由中介数求排列:

11.递增进位制数法:

12.递减进位制数法:

13.邻位对换法:

14.邻位对换法全排列:

15.邻位对换法的下一个排列:

16.邻位对换法的中介数:

17.组合数的字典序与生成:

由于本文的,内容比较多,所以希望读者根据自己的要求阅读,不要一次性读完,有些章节可以分开读。第1节到第5节提供了全排列的概念和一个初始的算法。第6节到第8节主要讲述了字典序的全排列算法。第9到第10节讲了有关字典序中中介数的概念。第11到第12节主要介绍了不同的中介数方法,仅供扩展用。第13节到15节介绍了邻位对换法的全排的有关知识。16节讲了有关邻位对换法的中介数,仅供参考。第17节讲了组合数生成的算法。

1.全排列的定义和公式:

从n个数中选取m(m

2.时间复杂度:

n个数(字符、对象)的全排列一共有n!种,所以全排列算法至少时O(n!)的。如果要对全排列进行输出,那么输出的时间要O(n*n!),因为每一个排列都有n个数据。所以实际上,全排列算法对大型的数据是无法处理的,而一般情况下也不会要求我们去遍历一个大型数据的全排列。

3.列出全排列的初始思想:

解决一个算法问题,我比较习惯于从基本的想法做起,我们先回顾一下我们自己是如何写一组数的全排列的:1,3,5,9(为了方便,下面我都用数进行全排列而不是字符)。

1/16页

1,3,5,9.(第一个)

首先保持第一个不变,对3,5,9进行全排列。

同样地,我们先保持3不变,对5,9进行全排列。

保持5不变,对9对进行全排列,由于9只有一个,它的排列只有一种:9。接下来5不能以5打头了,5,9相互交换,得到

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当中,因为原来数组的顺序它已经不知道了,这样便产生了错误。对于算法的设计,我们也可以维护这样一个数组,它保存了原始的数据,这是一种方法。同时我们还可以再每次交换后再交换回来,变回原来的数组,这样程序在遍历的时候便不会出错。读者可以练习一下这个过程,思考一下你是如何进行全排列的,当然,你的方法可能和我的不太一样。

我们把上面全排列的方法归纳一下,基本上就是:任意选一个数(一般从小到大或者从左到右)打头,对后面的n-1个数进行全排列。聪明的读者应该已经发现,这是一个递归的方法,因为要得到n-1个数的全排列,我们又要先去得到n-2个数的全排列,而出口是只有1个数的全排列,因为它只有1种,为它的本身。写成比较规范的流程:

1.开始for循环。

2.改变第一个元素为原始数组的第一个元素(什么都没做)。

3.求第2个元素到第n个元素的全排列。

4.要求第2个元素到第n个元素的全排列,要递归的求第3个元素到第n个元素的全排列。 ......

5.直到递归到第n个元素到第n元素的全排列,递归出口。

6.将改变的数组变回。

7.改变第一个元素为原始数组的第二个元素。

(注:理论上来说第二次排列时才改变了第一个元素,即第6步应该此时才开始执行,但由于多执行一次无义的交换影响不大,而这样使得算法没有特殊情况,更容易读懂,如果一定要省时间可以把这步写在此处,这种算法我在下文中便不给出了,读者可以自己写。)

5.求第2个元素到第n个元素的全排列。

6.要求第2个元素到第n个元素的全排列,要递归的求第3个元素到第n个元素的全排列。 ......

5.直到递归到第n个元素到第n元素的全排列,递归出口。

6.将改变的数组变回。

......

8.不断地改变第一个元素,直至n次使for循环中止。

为了实现上述过程,我们要先得到从第m个元素到第n个元素的排列的算法:

2/16页

4.从第m个元素到第n个元素的全排列的算法:

void Permutation(int A[], int m, int n)

{

if(m = = n)

{

Print(A); //直接输出,因为前n-1个数已经确定,递归到只有1个数。

return;

}

else

{

for(i=m;i

{

swap(a[m],a[i]); //交换,对应第二步

Permutation(A,m 1,n); //递归调用,对应三至五步

swap(a[m],a[i]); //交换,对应第六步

}

}

为了使代码运行更快,Print函数和swap函数直接写成表达式而不是函数(如果是C 的话建议把swap写成内联函数,把Print写成宏)

void Permutation(int A[], int m, int n)

{

int i, int temp;

if(m = = n)

{

for(i = 0;i  {

if(i != n-1)

printf('%d ',A[i]); //有加空格

else

printf('%d' A[i]); //没加空格

} //直接输出,因为前n-1个数已经确定,递归到只有1个数。

printf('\n');

return;

}

else

{

for(i=m;i  3/16页

是递归调用,对应的是第m个元素到第n个元素的全排列。*/

{

temp = A[m];

A[m] = A[i];

A[i] = temp; //交换,对应第二步

Permutation(A,m 1,n); //递归调用,对应三至五步

temp = A[m];

A[m] = A[i];

A[i] = temp;

//交换,对应第六步

}

}

}

这个算法用于列出从第m个元素到第n个元素的所有排列,注意n不一定是指最后一个元素。算法的复杂度在于for循环和递归,最大的for是n,递归为n-1所以为

O(n*n-1*n-2*...1) = O(n!).

对上述算法进行封装,便可以得到列出全排列的函数:

5.全排列算法:

void Full_Array(int A[],int n)

{

Permutation(A, 0,n);

}

如果读者仅仅需要一个全排列的递归算法,那么看到上面就可以了。下面将对全排列的知识进行扩充。

6.全排列的字典序:

字典序的英语一般叫做dictionary order,浅显明白。

定义:对于一个序列a1,a2,a3,a4,a5....an的两个排列b1,b2,b3,b4,b5...bn和c1,c2,c3,c4,c5...cn, 如果它们的前k(常数)项一样,且c(k 1)> b(k 1),则称排列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按字典序的排列结果:

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的输出结果的顺序是不同的,这样便没有统一性,而字典序则可以解决这个问题。很明显地,对于一个元素各不相同的元素集合,它的每个排列的字典序位置都不相同,有先有后。

接下来讲讲如何求某一个排列的紧邻着的后一个字典序。对证明不感兴趣的读者只要读下面

4/16页

加色的字即可。

定理:我们先来构造这样一个在它后面的字典序,再证明这是紧邻它的字典序。对于一个排列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(m)【如果a(n) 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项保持不变,得到的显然也是原排列后面的字典序,这个字典序便是紧挨着排列的后一个字典序。

下面证明它是紧挨着的。1.如果还存在前m-1项和原排列相同并且也在原排列后面的字典序a1,a2,a3...bm,...,bm>原am,假设它在我们构造的字典序前面,那么必有bm

证明完成后,我们便可以通过上述的构造方法求得一个排列的下一个字典序排列了。

7.求下一个字典序排列算法:

bool Next_Permutation(int A[], int n)

{

int i,m,temp;

for(i = n-2;i >= 0;i--)

{

if(A[i 1] > A[i])

break;

}

if(i

return false;

m = i;

i ;

for(;i  if(A[i]

{

i--;

break;

}

swap(A[i],A[m]);

5/16页

全排列以及相关算法

在程序设计过程中,我们往往要对一个序列进行全排列或者对每一个排列进行分析。全排列算法便是用于产生全排列或者逐个构造全排列的方法。当然,全排列算法不仅仅止于全排列,对于普通的排列,或者组合的问题,也可以解决。本文主要通过对全排列以及相关算法的介绍和讲解、分析,让读者更好地了解这一方面的知识,主要涉及到的语言是C和C 。 本文的节数:

1.全排列的定义和公式:

2.时间复杂度:

3.列出全排列的初始思想:

4.从第m个元素到第n个元素的全排列的算法:

5.全排列算法:

6.全排列的字典序:

7.求下一个字典序排列算法:

8.C STL库中的next_permutation()函数:(#include)

9.字典序的中介数,由中介数求序号:

10.由中介数求排列:

11.递增进位制数法:

12.递减进位制数法:

13.邻位对换法:

14.邻位对换法全排列:

15.邻位对换法的下一个排列:

16.邻位对换法的中介数:

17.组合数的字典序与生成:

由于本文的,内容比较多,所以希望读者根据自己的要求阅读,不要一次性读完,有些章节可以分开读。第1节到第5节提供了全排列的概念和一个初始的算法。第6节到第8节主要讲述了字典序的全排列算法。第9到第10节讲了有关字典序中中介数的概念。第11到第12节主要介绍了不同的中介数方法,仅供扩展用。第13节到15节介绍了邻位对换法的全排的有关知识。16节讲了有关邻位对换法的中介数,仅供参考。第17节讲了组合数生成的算法。

1.全排列的定义和公式:

从n个数中选取m(m

2.时间复杂度:

n个数(字符、对象)的全排列一共有n!种,所以全排列算法至少时O(n!)的。如果要对全排列进行输出,那么输出的时间要O(n*n!),因为每一个排列都有n个数据。所以实际上,全排列算法对大型的数据是无法处理的,而一般情况下也不会要求我们去遍历一个大型数据的全排列。

3.列出全排列的初始思想:

解决一个算法问题,我比较习惯于从基本的想法做起,我们先回顾一下我们自己是如何写一组数的全排列的:1,3,5,9(为了方便,下面我都用数进行全排列而不是字符)。

1/16页

1,3,5,9.(第一个)

首先保持第一个不变,对3,5,9进行全排列。

同样地,我们先保持3不变,对5,9进行全排列。

保持5不变,对9对进行全排列,由于9只有一个,它的排列只有一种:9。接下来5不能以5打头了,5,9相互交换,得到

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当中,因为原来数组的顺序它已经不知道了,这样便产生了错误。对于算法的设计,我们也可以维护这样一个数组,它保存了原始的数据,这是一种方法。同时我们还可以再每次交换后再交换回来,变回原来的数组,这样程序在遍历的时候便不会出错。读者可以练习一下这个过程,思考一下你是如何进行全排列的,当然,你的方法可能和我的不太一样。

我们把上面全排列的方法归纳一下,基本上就是:任意选一个数(一般从小到大或者从左到右)打头,对后面的n-1个数进行全排列。聪明的读者应该已经发现,这是一个递归的方法,因为要得到n-1个数的全排列,我们又要先去得到n-2个数的全排列,而出口是只有1个数的全排列,因为它只有1种,为它的本身。写成比较规范的流程:

1.开始for循环。

2.改变第一个元素为原始数组的第一个元素(什么都没做)。

3.求第2个元素到第n个元素的全排列。

4.要求第2个元素到第n个元素的全排列,要递归的求第3个元素到第n个元素的全排列。 ......

5.直到递归到第n个元素到第n元素的全排列,递归出口。

6.将改变的数组变回。

7.改变第一个元素为原始数组的第二个元素。

(注:理论上来说第二次排列时才改变了第一个元素,即第6步应该此时才开始执行,但由于多执行一次无义的交换影响不大,而这样使得算法没有特殊情况,更容易读懂,如果一定要省时间可以把这步写在此处,这种算法我在下文中便不给出了,读者可以自己写。)

5.求第2个元素到第n个元素的全排列。

6.要求第2个元素到第n个元素的全排列,要递归的求第3个元素到第n个元素的全排列。 ......

5.直到递归到第n个元素到第n元素的全排列,递归出口。

6.将改变的数组变回。

......

8.不断地改变第一个元素,直至n次使for循环中止。

为了实现上述过程,我们要先得到从第m个元素到第n个元素的排列的算法:

2/16页

4.从第m个元素到第n个元素的全排列的算法:

void Permutation(int A[], int m, int n)

{

if(m = = n)

{

Print(A); //直接输出,因为前n-1个数已经确定,递归到只有1个数。

return;

}

else

{

for(i=m;i

{

swap(a[m],a[i]); //交换,对应第二步

Permutation(A,m 1,n); //递归调用,对应三至五步

swap(a[m],a[i]); //交换,对应第六步

}

}

为了使代码运行更快,Print函数和swap函数直接写成表达式而不是函数(如果是C 的话建议把swap写成内联函数,把Print写成宏)

void Permutation(int A[], int m, int n)

{

int i, int temp;

if(m = = n)

{

for(i = 0;i  {

if(i != n-1)

printf('%d ',A[i]); //有加空格

else

printf('%d' A[i]); //没加空格

} //直接输出,因为前n-1个数已经确定,递归到只有1个数。

printf('\n');

return;

}

else

{

for(i=m;i  3/16页

是递归调用,对应的是第m个元素到第n个元素的全排列。*/

{

temp = A[m];

A[m] = A[i];

A[i] = temp; //交换,对应第二步

Permutation(A,m 1,n); //递归调用,对应三至五步

temp = A[m];

A[m] = A[i];

A[i] = temp;

//交换,对应第六步

}

}

}

这个算法用于列出从第m个元素到第n个元素的所有排列,注意n不一定是指最后一个元素。算法的复杂度在于for循环和递归,最大的for是n,递归为n-1所以为

O(n*n-1*n-2*...1) = O(n!).

对上述算法进行封装,便可以得到列出全排列的函数:

5.全排列算法:

void Full_Array(int A[],int n)

{

Permutation(A, 0,n);

}

如果读者仅仅需要一个全排列的递归算法,那么看到上面就可以了。下面将对全排列的知识进行扩充。

6.全排列的字典序:

字典序的英语一般叫做dictionary order,浅显明白。

定义:对于一个序列a1,a2,a3,a4,a5....an的两个排列b1,b2,b3,b4,b5...bn和c1,c2,c3,c4,c5...cn, 如果它们的前k(常数)项一样,且c(k 1)> b(k 1),则称排列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按字典序的排列结果:

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的输出结果的顺序是不同的,这样便没有统一性,而字典序则可以解决这个问题。很明显地,对于一个元素各不相同的元素集合,它的每个排列的字典序位置都不相同,有先有后。

接下来讲讲如何求某一个排列的紧邻着的后一个字典序。对证明不感兴趣的读者只要读下面

4/16页

加色的字即可。

定理:我们先来构造这样一个在它后面的字典序,再证明这是紧邻它的字典序。对于一个排列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(m)【如果a(n) 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项保持不变,得到的显然也是原排列后面的字典序,这个字典序便是紧挨着排列的后一个字典序。

下面证明它是紧挨着的。1.如果还存在前m-1项和原排列相同并且也在原排列后面的字典序a1,a2,a3...bm,...,bm>原am,假设它在我们构造的字典序前面,那么必有bm

证明完成后,我们便可以通过上述的构造方法求得一个排列的下一个字典序排列了。

7.求下一个字典序排列算法:

bool Next_Permutation(int A[], int n)

{

int i,m,temp;

for(i = n-2;i >= 0;i--)

{

if(A[i 1] > A[i])

break;

}

if(i

return false;

m = i;

i ;

for(;i  if(A[i]

{

i--;

break;

}

swap(A[i],A[m]);

5/16页


相关内容

  • 全排列算法解析(完整版)
  • 全排列以及相关算法 在程序设计过程中,我们往往要对一个序列进行全排列或者对每一个排列进行分析.全排列算法便是用于产生全排列或者逐个构造全排列的方法.当然,全排列算法不仅仅止于全排列,对于普通的排列,或者组合的问题,也可以解决.本文主要通过对全排列以及相关算法的介绍和讲解.分析,让读者更好地了解这一方 ...

  • 圆排列问题
  • 自己收藏的 觉得很有用 故上传到百度 与大家一起分享! 圆排列问题 一.问题描述 给定N个大小不等的圆C1 C2 ... 现将这N个圆排进一个矩形框中 且要求个圆与矩形框的底边相切 圆排列问题要求从N个圆的所有排 二.算法设计 圆排列问题的解空间是一棵排列树 按照回溯法搜索排列树的算法框架 设开始时 ...

  • matlab中回溯算法
  • 第 4 章 回溯 寻找问题的解的一种可靠的方法是首先列出所有候选解,然后依次检查每一个,在检查完所有或部分候选解后,即可找到所需要的解.理论上,当候选解数量有限并且通过检查所有或部分候选解能够得到所需解时,上述方法是可行的.不过,在实际应用中,很少使用这种方法,因为候选解的数量通常都非常大(比如指数 ...

  • 信息安全保密期末考试复习
  • 计科 期末考试复习 <信息安全与保密>课程 期末 考试试卷( B 卷) 考试专业班级计算机科学与技术考试形式 闭卷 考试类型 考查 考试时间 120 分钟 题号 分值 一. 一 二 三 四 五 六 七 总分 26 38 10 10 16 100 填空题,请把答案填写在答题纸上.(每空1分 ...

  • 第4章贪心算法(0-算法思想)
  • 第4章 贪心算法 1 学习要点  贪心算法的基本思想  贪心算法的基本要素 (1)最优子结构性质 (2)贪心选择性质 贪心算法与动态规划算法的差异 正确性的证明 范例学习贪心设计策略 (1)活动安排问题: (4)单源最短路径: (2)最优装载问题: (5)最小生成树: (3)哈夫曼编码: (6) ...

  • 用排列组合算法,计算彩票总注数
  • 用排列组合算法,计算彩票总注数 C-Combination 组合数 A-Arrangement 排列数 N-元素的总个数 M-参与选择的元素个数 C n = n!/(n-m)!=n*(n-1)*--*(n-m+1)/m*(m-1*)--*1 A n = n!/(n-m)!/m!=n*(n-1)*-- ...

  • 20151112-JWJ-Algorithm-12-贪心算法
  • 湖南大学-算法设计与分析课程 Lecture 12-贪心算法 姜文君 [email protected] 办公室:信息科学与工程学院院楼326 2015-2016第一学期 回 顾 动态规划6案例, 矩阵连乘.最长公共子序列. 最大子段和可扩展. 凸多边形最优三角剖分. 多边形游戏更一般. 0-1背包为经 ...

  • 算法设计与分析部分算法伪代码
  • 第三章 蛮力法 1. 选择排序 ⏹ SelectionSort(A[0..n-1]) for i=0 to n-2 do min=i for j=i+1 to n-1 do if A[j] 2. 冒泡排序 ⏹ BubbleSort(A[0..n-1]) // 输入:数组A ,数组中的元素属于某偏序集 ...

  • 北大算法分析与设计课件8
  • 第8 章近似算法 8.1 近似算法及其近似比 8.2 多机调度问题 8.2.1 贪心的近似算法 822改进的贪心近似算法8.2.2 8.3 货郎问题 8.3.1 最邻近法 8.3.2 最小生成树法 833最小权匹配法8.3.3 8.4 背包问题 8.4.1 841一个简单的贪心算法 8.4.2 多项 ...