参考:http://blog.csdn.net/laojiu_/article/details/51115352
1.非字典序:
首先是非字典序不能重复的递归实现:
以 1 , 2 , 3 , 4 为例 , 想要得到全排列,必定是 先以 一个数字开头,然后求剩下数字的全排列:
以 1 开头,下面跟着 {2, 3, 4} 的全排列;
-
以 2 开头,下面跟着 {1, 3, 4} 的全排列;
-
以 3 开头,下面跟着 {1, 2, 4} 的全排列;
-
以 4 开头,下面跟着 {1, 2, 3} 的全排列。
得到递归算法:
/**********非字典序不可以重复**************/ #include <bits/stdc++.h> using namespace std; char a[10]; //这里我用的是char类型的示范 void solve(char a[] , int left , int right ){ if( left == right ){ for(int i = 0 ; i <= right ; i ++ ){ cout<< a[i] ; } cout<<endl; }else{ for( int i = left ; i <= right ; i++ ){ swap( a[i] , a[left] ) ; solve(a , left + 1 , right ); swap( a[i] , a[left] ); } } } int main(){ scanf("%s",a); solve(a , 0 , strlen(a)-1); return 0; }
接着是非字典序不可以重复:只需要判断交换是否相等, 不相等才能交换
为什么那样判断?举个例子:对于 1abc2xyz2 这样的排列,我们交换1与第一个2,变成2abc1xyz2,按照递归的顺序,接下来对 abc1xyz2进行全排列;但是1是不能和第二个2交换的,如果交换了,变成了2abc2xyz1,按照递归的顺序,接下来对 abc2xyz1进行全排列,那么问题来了,注意我红色突出的两个地方,这两个全排列进行的都是同样的工作,也就是如果1和第二个2交换必然会和前面重复。
/*************非字典序可以重复*****************/ #include <iostream> #include <cstdio> #include <string.h> using namespace std; char a[10]; bool judge(int s ,int e){ //不相等才能交换 for( int i = s ; i < e; i ++ ){ if( a[i] == a[e] ) return false; } return true; } void permutation(int left , int right){ if(left == right){ for( int i = 0 ; i <= right ; i++ ){ cout<<a[i]; } cout<<endl; } for( int i = left ;i <= right ; i++ ){ if( judge( left , i ) ){ //不想等才交换 swap( a[i] , a[left] ); permutation(left + 1 , right); swap( a[i] , a[left] ); } } } int main(){ scanf("%s",a); int len = strlen(a); permutation(0 , len-1); return 0; }
2.字典序算法
首先看什么叫字典序,顾名思义就是按照字典的顺序(a-z, 1-9)。以字典序为基础,我们可以得出任意两个数字串的大小。比如 "1" < "12"<"13"。 就是按每个数字位逐个比较的结果。对于一个数字串,“123456789”, 可以知道最小的串是 从小到大的有序串“123456789”,而最大的串是从大到小的有序串“*987654321”。这样对于“123456789”的所有排列,将他们排序,即可以得到按照字典序排序的所有排列的有序集合。
如此,当我们知道当前的排列时,要获取下一个排列时,就可以范围有序集合中的下一个数(恰好比他大的)。比如,当前的排列时“123456879”, 那么恰好比他大的下一个排列就是“123456897”。 当当前的排列时最大的时候,说明所有的排列都找完了。
于是可以有下面计算下一个排列的算法:
设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+1......pn倒转得到排列p'=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1,这就是排列p的下一个排列。
下面以51nod1384为例(含有重复)
基准时间限制: 1 秒 空间限制: 131072 KB 分值: 0 难度:基础题给出一个字符串S(可能有重复的字符),按照字典序从小到大,输出S包括的字符组成的所有排列。例如:S = "1312",输出为:
112311321213123113121321211321312311311231213211Input输入一个字符串S(S的长度 <= 9,且只包括0 - 9的阿拉伯数字)
Output输出S所包含的字符组成的所有排列
Input示例1312
Output示例1123 1132 1213 1231 1312 1321 2113 2131 2311 3112 3121 3211
/***********字典序可以重复*********************/ #include <iostream> #include <cstdio> #include <algorithm> #include <string.h> using namespace std; char a[10]; int c[10]; void permutation(){ int j , k ; while(true){ for( int i = 0 ; i < strlen(a) ; i++ ) cout<<c[i]; cout<<endl; for( j = strlen(a) - 2 ; j >=0 && c[ j ] >= c[ j + 1 ] ; j -- ); //只需要加个=号 if( j < 0 ) return ; for( k = strlen(a) - 1 ; k > j && c[k] <= c[j] ; k -- ); //只需要加个=号 swap( c[j] , c[k] ); for( int l = j + 1 , r = strlen(a) - 1 ; l < r ; l ++ , r -- ) swap( c[l] , c[r] ); } } int main(){ scanf("%s",a); int len = strlen(a); for( int i = 0 ; i < len ; i++ ){ c[i] = a[i] - '0'; } //cout<<len; sort( c, c+len ); permutation(); return 0; }
另外也可以用STL容器实现(自己写的快了1倍)。
#include <iostream> #include <algorithm> #include <cstdio> #include <string.h> using namespace std; char a[10]; int c [10]; void FullPermutation(int c[]){ do{ for( int i = 0 ; i < strlen(a) ;i ++ ){ cout<<c[i]; } cout<<endl; }while( next_permutation( c , c + strlen(a) ) ); } int main(){ scanf("%s",a); for( int i = 0 ; i < strlen(a) ; i++ ){ c[i] = a[i] - '0'; } sort(c,c+strlen(a)); FullPermutation(c); return 0; }
- STL 的两个全排列函数。不用考虑重复与否且为字典序,但是因为实现复杂效率低
- next_permutation,首先,从最尾端开始往前寻找两个相邻元素,令第一元素为
*i
,第二元素为*ii
,且满足*i<*ii
,找到这样一组相邻元素后,再从最尾端开始往前检验,找出第一个大于*i
的元素,令为*j
,将 i,j 元素对调,再将 ii 之后的所有元素颠倒排列,此即所求之 “下一个” 排列组合。 -
prev_permutation,首先,从最尾端开始往前寻找两个相邻元素,令第一元素为
*i
,第二元素为*ii
,且满足*i>*ii
,找到这样一组相邻元素后,再从最尾端开始往前检验,找出第一个小于*i
的元素,令为*j
,将 i,j 元素对调,再将 ii 之后的所有元素颠倒排列,此即所求之 “上一个” 排列组合。bool next_permutation(int * first, int * last) { if (first == last) return false; //空区间 int * i = first; ++i; if (i == last) return false; //只有一个元素 i = last; --i; for (;;) { int * ii = i; --i; if (*i < *ii) { int * j = last; while (!(*i < *--j)) //由尾端往前找,直到遇上比*i大的元素 ; swap(*i, *j); reverse(ii, last); return true; } } if (i == first) //当前排列为字典序的最后一个排列 { reverse(first, last); //全部逆向排列,即为升序 return false; } } bool prev_premutation(int * first, int * last) { if (first == last) return false; //空区间 int * i = first; ++i; if (i == last) return false; //只有一个元素 i = last; --i; for (;;) { int * ii = i; --i; if (*i > *ii) { int * j = last; while (!(*i > *--j)) //由尾端往前找,直到遇上比*i大的元素 ; swap(*i, *j); reverse(ii, last); return true; } } if (i == first) //当前排列为字典序的第一个排列 { reverse(first, last); //全部逆向排列,即为降序 return false; } }