字符串全排列算法
问题描述
给出一个字符串(可能包含字母和数字)给出该字符串中字符的所有非重复排列
例子
输入:abc
输出:abc, acb, bac, bca, cab, cba
分析
1.典型的递归方法
- 递归问题分解
假设求解长度为n字符串的全排列,将字符串划分为首字符和剩余的n-1个字符串,将首字符与字符串中每个字符进行交换,加上每次交换后剩余长度n-1个字符串的全排列。
对于n-1个字符串的全排列,又可以继续分解为首字符与n-2个字符串的全排列,接下来按照相同步骤进行递归分解。 - 递归终止条件
按照上述递归问题的分解,当字符串中只剩下最后一个字符时,不需要进行交换,此时递归达到终止条件。
实例分析
下面对上述例子字符串“abc”进行分析,此时字符串长度为3,首字符为‘a’。
第一层递归:
1 ‘a’与第一个字符交换,即与本身交换,字符串不变为“abc”,首字符依旧为‘a’,问题分解为求长度为2的字符串“bc”的全排列。
第二层递归
1.1 ‘b’与第一个字符交换,即与本身交换,字符串不变为“bc”,首字符依旧为‘b’,问题分解为求长度为1的字符串“c”的全排列。
第三层递归
1.1.1 此时字符串长度为1,达到递归终止条件,输出当前排列“abc”。
跳出第三层递归
第二层递归
1.2 ‘b’与第二个字符‘c’交换,字符串变为“cb”,首字符为’c’,问题分解为求长度为1的字符串“b”的全排列。
第三层递归
1.2.1 此时字符串长度为1,达到递归终止条件,输出当前排列“acb“。
跳出第三层递归
1.2.2 将序列还原成原来样子“bc”,如果不这样的话字符串顺序会混乱,无法依次交换首字符和后面的字符。
跳出第二层递归
2 ‘a’与第二个字符交换,字符串不变为“bac”,首字符为‘b’,问题分解为求长度为2的字符串“ac”的全排列。
后面的可以同理得出…
存在问题
此时当存在相同的字符进行交换时,会出现重复排列,去除重复排列时只需将交换条件设置为不相等时才交换。
如果需要对输出结果完全按照字母顺序排序,则可以使用set存储结果,直接解决了排序和去除重复两个问题。
非完全按照字母排序代码如下:
vector<string> Permutation(string str) {
vector<string> vec;
if(str.size()!=0)
core(str,str.begin());
return vec;
}
void core(string &str, string::iterator strIt)
{
if (strIt ==str.end() )//递归结束条件
vec.push_back(str);
else
{
for (string::iterator tmpIt = strIt; tmpIt != str.end(); ++tmpIt)
{
if(tmpIt!=strIt&&*tmpIt==*strIt)//去除重复字符交换,产生重复序列
continue;
//首字母与后面字母交换
char tmp = *tmpIt;
*tmpIt = *strIt;
*strIt = tmp;
core(str, strIt + 1);//进行递归
//恢复交换前顺序
tmp = *tmpIt;
*tmpIt = *strIt;
*strIt = tmp;
}
}
}
2.字典序方法(非递归)
每个排列可看作成一个字符串,要求前后两个字符串之间有尽可能长的共同前缀,避免了递归,更为高效,STL 中next_permutation、prev_permutation等函数就使用了这种算法,具体讲解和代码可以参考《STL源码解析》P380~383,这里就不赘述了。
但是这种算法依然无法避免出现重复序列问题,为了解决这种问题可以使用set解决,去掉重复项。