全排列
递归式
无重复元素的全排列
对于一个不含有重复字符的字符串4132其全排列的第一个字符分别是该字符串的每个字符,如1xxxx,2xxxx,3xxxx,4xxxx,那么对于剩下的部分(xxxx)又是一个全排列。伪代码如下:
void print_permutation(序列A,集合S)
{
if(S为空) 输出序列A;
else 遍历S中的每个元素e {
print_permutation(在A的尾部+e,S-{e});
}
}
具体实现我们有两种方法:
1. 用2个string类型的x和y,其中x保存已递归的字符,y保存未递归的字符,当前递归中将y中的每个字符加入x尾部,然后在y中删除该字符,继续递归。递归边界时y的大小=0。
2. 我们可以用一个数组a[]保存当前已访问字符串下标,一个标记数组vis[]保存是否已访问。遍历字符串未访问的字符,保存下标并置访问标记,递归处理,然后清除访问标记。递归边界是a[]的大小=字符串大小。
除此之外,全排列还可以用其他方法得到。全排列就是从第一个数字起每个数分别与它后面的数字交换,如下所示:
41532 | 14 532 | 154 32 | 1534 2 | 15324 |
---|---|---|---|---|
s[0]-s[1] | s[1]-s[2] | s[2]-[3] | s[3]-s[4] |
有重复元素的全排列
去重的全排列就是从第一个字符起每个数分别与它后面非重复出现的字符交换。第i个数与第j个字符交换时,要求[i,j)中没有与第j个字符相等的字符。
bool isVis(int ps,int end)
{
for(int i=ps;i<end;i++)
if(s1[i]==s1[end])
return true;
return false;
}
void print_permutation5(int ps,int len)
{
if(ps==len-1){
cout<<s1<<endl;
return;
}
for(int i=ps;i<len;i++) if(!isVis(ps,i)){
swap(s1[ps],s1[i]);
print_permutation5(ps+1,len);
swap(s1[ps],s1[i]);
}
}
源码实现
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<string>
#include<map>
#include<set>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<sstream>
#define LL long long
#define OJ_DEBUG 0
#define READ_FILE 0
using namespace std;
const int NN_MAX = 100;
const int MM_MAX = NN_MAX*NN_MAX;
const int INF = 0x3f3f3f3f;
/**********************************************************/
string s1;
/**********************************************************/
//不重复字符串全排列 递归
void print_permutation1(string x,const string y)
{
if(y.length()==0){
cout<<x<<endl;
return;
}
for(int i=0;i<y.length();i++){
string yy=y;
print_permutation1(x+y[i],yy.erase(i,1));
}
}
//不重复字符串全排列 递归 节省内存
int a[NN_MAX]={0};
bool vis[NN_MAX]={0};
void print_permutation2(int n, int len)
{
if(n==len){
for(int i=0;i<n;i++)
cout<<s1[a[i]-1];
cout<<endl;
return;
}
for(int i=0;i<len;i++) if(vis[i]==0){
a[n]=i+1; vis[i]=1;
print_permutation2(n+1,len);
vis[i]=0;
}
}
void print_permutation5(int ps,int len)
{
if(ps==len-1){
cout<<s1<<endl;
return;
}
for(int i=ps;i<len;i++){//i=ps循环len-ps次,最后n!次
swap(s1[ps],s1[i]);
print_permutation5(ps+1,len);
swap(s1[ps],s1[i]);
}
}
//不重复字符串全排列 非递归 字典序
void conOrder(int l,int r)
{
while(l<r)
{
swap(s1[l],s1[r]);
--r;
++l;
}
}
void print_permutation3()
{
sort(s1.begin(),s1.end());
int n=s1.length(),i;
while(1)
{
cout<<s1<<endl;
for(i=n-2;i>=0;i--) if(s1[i]<s1[i+1]){
int mm=INF,x;
for(int j=i+1;j<n;j++)
if(s1[j]-s1[i]>0 && s1[j]-s1[i]<mm)
mm=abs(s1[x=j]-s1[i]);
swap(s1[i],s1[x]);
conOrder(i+1,n-1);
break;
}
if(i<0) break;
}
}
void print_permutation4()
{
sort(s1.begin(),s1.end());
do{
cout<<s1<<endl;
}while(next_permutation(s1.begin(),s1.end()));
}
/**********************************************************/
int main()
{
string s2;
s1="1234";
//print_permutation1(s2,s1);
//print_permutation2(0,s1.length());
//print_permutation3();
//print_permutation4();
print_permutation5(0,s1.length());
return 0;
}
非递归式
非递归采用字典序方法,重复和非重复都适用。对于一个字符串41532,先排个序12345,然后每次得到比先前只大一点的字符串12354,直到最大54321。得到下一个排列的方法是:
从后往前,找到第一个s[i]<s[i+1],如果没有找到说明已最大。然后再从i+1开始往后遍历,找到比s[i]大的最小者j,交换s[i]和s[j]。然后再将i后面的字符串逆置,就得到下一个排列。
STL也提供了上述的实现算法:next_permutation()。