描述:输入一个字符串,打印出该字符串字符的所有排列。比如给定字符串“abc”,即求字符’a‘,’b‘,’c’的全排列,结果应该是abc、acb、bac、bca、cba和cab总有六个。
1、全排列的递归方法
思路:对于这个问题,我们从结果来分析,以abc和acb为例可以看出,相当于以a为字符串首字符,再和b、c的全排列cb、cb连接构成。以此类推,可以确定以b、c为首字符的全排列。因此解决这个问题可以使用递归的方法,要求n个字符的全排列,可以将n个字符的分解为两部分,第一个和剩下的n-1个,求这两个的全排列;而n-1个字符的全排列又可以通过递归求n-1个字符中的第一个和剩余n-2个字符的全排列;以此类推。
void Swap(char *a, char *b)
{
char t = *a;
*a = *b;
*b = t;
}
void Permutation(char *str, char *begin, char *end)
{
if(begin == end) // 递归中止条件
printf("%s\n", str);
else
{
// 从x向后扫描每一个字符,交换x和begin指向的字符后,再对
// begin后面的字符串递归调用排列操作,直到begin指向字符串末尾
bool flag = true;
for(char *x = begin; x < end; ++x)
{
// 避免重复全排列
for(char *y = x; y < end; ++y)
if(*x == *(y + 1))
flag = false;
if(!flag)
{
flag = true;
continue;
}
// 交换x和begin指向的字符
Swap(x, begin);
Permutation(str, begin + 1, end);
// 恢复x和begin指向的字符
Swap(x, begin);
}
}
}
上述实现方法由于递归调用引入调用栈,随着字符串长度的增长,函数执行效率降低的同时伴随着调用栈溢出的隐患。因此比较高效安全的解决方法是消除递归,用非递归的方法实现字符串的全排列。
2、全排列的非递归方法
C++ STL标准库提供的next_permutation方法可以用来计算在字典序中相邻的字符排列,如果存在下一个排列返回true,如果达到最大字典序,则返回false并重设该排列为最小字典序。求一个给定字符串的全排列,可以先对给定字符串升序排序,再调用next_permutation方法直到返回false,就得到了给定字符串的所有排列。
next_permutation函数原型如下:
template<class BidirectionalIterator>
bool next_permutation(
BidirectionalIterator _First,
BidirectionalIterator _Last
);
template<class BidirectionalIterator, class BinaryPredicate>
bool next_permutation(
BidirectionalIterator _First,
BidirectionalIterator _Last,
BinaryPredicate _Comp
);
在当前序列中,从尾端往前寻找两个相邻元素,前一个记为*i,后一个记为*ii,并且满足*i < *ii。然后再从尾端寻找另一个元素*j,如果满足*i < *j,即将第i个元素与第j个元素对调,并将第ii个元素之后(包括ii)的所有元素颠倒排序,即求出下一个序列了。
template<class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last)
{
if(first == last)
return false; //空序列
BidirectionalIterator i = first;
++i;
if(i == last)
return false; //一个元素,没有下一个序列了
i = last;
--i;
for(;;) {
BidirectionalIterator ii = i;
--i;
if(*i < *ii) {
BidirectionalIterator j = lase;
while(!(*i < *--j));
iter_swap(i, j);
reverse(ii, last);
return true;
}
if(i == first) {
reverse(first, last); //全逆向,即为最小字典序列,如cba变为abc
return false;
}
}
}
依据上述源码实现,可以对其修改形成非递归调用的方法,具体的修改方法及next_permutation使用方法这里不多作阐述,详细信息可以参考STL源码分析相关书籍。3、运行时间分析
递归调用时间消耗: abcdefghijk 5.28s
非递归调用时间消耗: abcdefghijk 3.84s
递归调用时间消耗: abcdefghijkl 63.01s
非递归调用时间消耗: abcdefghijkl 45.65s
从上述运行结果可以看到,当数据时较小时,递归调用和非递归调用的差异不是很明显,但是随着数据量的增长,特别当字符串的长度大于12时,非递归调用的优势就得以体现。