字符串的排列

描述:输入一个字符串,打印出该字符串字符的所有排列。比如给定字符串“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时,非递归调用的优势就得以体现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值