(先稳定一下军心,下面所有代码都测试过,可以直接用,所以就没有截图,想看效果的可以自己运行一下)
1.全排列
全排列:字符以任意顺序排列,比如123的全排列是123,132,213,231,312,321。长度为n的字符串的全排列个数为n!个,下面是代码:
/*思路:s全排列是第一位与s[1~n-1]的全排列,所以每次交换第一位与
** s中的某一位,然后递归求出s[1~n-1]的全排列。
*/
#include<iostream>
#include <string>
using namespace std;
static int count=0;
void Permutation(string s,int beg,int sLen)
{
if(0==sLen) //这轮全排列结束
{
++count;
cout<<s<<endl;
return;
}
for (int i=0;i<sLen;++i)
{
swap(s[beg],s[beg+i]); //将s中的每一位与第一位交换
Permutation(s,beg+1,sLen-1); //排列s[1~n-1]
swap(s[beg+i],s[beg]); //每一轮完后将交换后的字符还原
}
}
int main()
{
string s;
cout<<"请输入字符串:";
cin>>s;
int sLen=s.size();
Permutation(s,0,sLen);
cout<<endl<<"共"<<count<<"个"<<endl<<endl;
return 0;
}
void Permutation(string s,int beg,int end)
{
if(beg==end-1) //这轮全排列结束
{
++count;
cout<<s<<endl;
return;
}
for (int i=beg;i<end;++i)
{
swap(s[beg],s[i]);
Permutation(s,beg+1,end);
swap(s[i],s[beg]);
}
}
在上面的代码中,如果字符串中有重复的字符,就不能避免重复输出了。例如对于1213,n!=4!=24,如果考虑到避免重复输出,应该有4!/2!=12个。下面的代码是避免全排列中的重复输出:
/* 思路:1.轮流选取s中的每位作为第一位。如果第i位字符在[0,i-1]这个区间内出现过,
** 就不再对其进行排列,因为把它放在第一位的所有排列在前面排列过了(就是代码中j作为循环变量的作用)。
** 2.把当前选取的这一位放入目标数组中。
** 3.用一个临时数组保存剩余的字符,并把其作为源。同时目标数组的地址下移一位,因为当前位已经排列好。
*/
#include<iostream>
#include <string>
using namespace std;
static int count=0; //记录全排列个数
char *result=new char[16](); //存放某个排列
void Permutation(string strSource,char *strResult,int sLen)
{
if(sLen==1) //这轮全排列结束
{
strResult[0]=strSource[0]; //只有一个字符
++count;
//strResult在递归的过程中是一次向后移一位的,所以字符串的首地址是result
cout<<result<<endl;
return;
}
for (int i=0;i<sLen;++i)
{
int j;
for ( j=0;j<i&&strSource[j]!=strSource[i];j++); //判断当前字符是否在之前已经出现过
if (j==i) //没有出现过
{
strResult[0]=strSource[i]; //把当前字符放在第一位
//将除了当前位剩下的字符存入nextRecursion中,进入下一次递归
string nextRecursion=strSource.substr(0,i)+strSource.substr(i+1,sLen-i-1);
Permutation(nextRecursion,strResult+1,sLen-1); //strResult向后移一位,构造下个字符
}
}
}
int main()
{
string s;
cout<<"请输入字符串:";
cin>>s;
int sLen=s.size();
Permutation(s,result,sLen);
cout<<endl<<"共"<<count<<"个"<<endl<<endl;
return 0;
}
2.排列
在第一节中讨论了字符串全排列的问题,在这一节讨论排列的问题。
排列:从长度为n的字符串中任意挑选m个元素以任意次序排列。全排列是n=m的特殊情况。
编程思路和上面的代码很像。最大的不同之处就是把结束时的情况放到了for循环里面,并且把return去掉了,这是为了保证所有的情况都能遍历到。还增加了一个参数k来记录已经取得的字符个数。
#include<iostream>
#include <string>
using namespace std;
static int count=0; //记录全排列个数
char *result=new char[16](); //存放某个排列
//sLen是源字符串的长度,m是要取的字符个数,所以排列个数是A(sLen,m)
//k为已经取得的字符个数
void Permutation(string strSource,char *strResult,int sLen,int m,int k)
{
for (int i=0;i<sLen;++i)
{
int j;
for ( j=0;j<k&&result[j]!=strSource[i];j++); //判断当前字符是否在之前已经出现过
if (j==k) //没有出现过
{
if(k==m-1) //这轮全排列结束
{
strResult[0]=strSource[i];
++count;
//strResult在递归的过程中是一次向后移一位的,所以字符串的首地址是result
cout<<result<<endl;
// return;
}
else
{
strResult[0]=strSource[i]; //把当前字符放在第一位
//将除了当前位剩下的字符存入nextRecursion中,进入下一次递归
string nextRecursion=strSource.substr(0,i)+strSource.substr(i+1,sLen-i-1);
Permutation(nextRecursion,strResult+1,sLen-1,m,k+1); //strResult向后移一位
}
}
}
}
int main()
{
string s;
int m;
cout<<"请输入字符串:";
cin>>s;
cout<<"请输入m:";
cin>>m;
int sLen=s.size();
if(m<=sLen)
Permutation(s,result,sLen,m,0);
cout<<endl<<"共"<<count<<"个"<<endl<<endl;
return 0;
}
ps:为了和之前的代码保持连贯性,并且突出修改的重点(1.终止条件放到for循环中;2.去掉return),代码中的一些优化我都没有改。比如说,可以把if和esle中的strResult[0]=strSource[i]; 提出来;28行和29行的截取字符串是多余的,因为13行的代码已经判断重复问题了,所以28行和29行直接可以换成Permutation(strSource,strResult+1,sLen,m,k+1);
这个代码和上面的代码很像,这里可以好好体会一下这两个代码的不同之处。为了和数学中的排列定义A(n,m)相符,我把代码中的n换成了m,sLen相当于这里的n 。还需要说明的一点就是这段代码只能运用在无重复字符的字符串中,若有重复字符,会有重复输出。