算法-字符串的全排列

来源:编程之法:面试和算法心得 July

题目描述:输入一个字符串,打印出该字符串中字符的所有排列。例如,输入字符串“abc”,则输出“abc”、“acb”、“bac”、“bca”、“cab”和“cba”。

分析与解法:

解法一:递归实现 (递归理解起来也挺费劲,就像当初理解汉诺塔类似)

从字符串中选出一个字符作为排列的第一个字符,然后对剩余的字符进行全排列。如此递归处理,从而得到所有字符的全排列。

参考代码:

void CalcAllPermutation (char * perm, int from, int to)
{
	if (to <= 1)
	{
		return;
	}
	if (from == to)
	{
		for (int i = 0; i <= to; i++)
		{
			count << perm[i];
		}
		cout << endl;
	}
	else
	{
		for (int j = from; j <= to; j++)
		{
			swap(perm[j], perm[from]);
			CalcAllPermutation(perm, form+1, to);
			swap(perm[j], perm[from]);
		}
	}
}

解法二:字典序排列

维基百科定义:给定两个偏序集A和B,(a, b)和(a’,b’)属于笛卡尔积 A * B,则字典序定义为(a, b)<=(a’,b’)当且仅当a <a' 或a = a'且b <= b'。

根据上述字典序定义可知,如果给定两个字符串,从起点开始将它们对应的字符逐个进行比较,则先出现较小字符的那个字符串的字典序小;如果字符一直相等,则较短的那个字符串的字典序小。那么,有没有执行的起点、终点、过程如下的算法呢?

算法起点:字典序最小的排列1-n,如“12345”。

算法终点:字符序最大的排列n-1,如“54321”。

算法的执行过程:从当前排列生成字典序刚好比它大的下一个排列。

答案是肯定的,这就是C++标准库中的next_permutation函数实现 的算法。

假定现有字符串AxB,它的下一个排列是AyB',其中A、B和B'是字符串(可能为空),x和y是字符,前缀相同,都是A,且一定有y > x。那么,为了使下一个排列字典顺序尽可能小,必有:A尽可能长,而y尽可能小,最后B'里的字符按由小到大递增排列。现在的问题是,如何确定x和y呢?

来看一个例子。例如,要找21543的下一个排列,则可以从左到右逐个扫描每个数,看哪个能增大(即如果一个数的右面有比它大的数存在,那么这个数就能增大),可以看到最后一个能增大的数是:x = 1。而1应该增大到多少呢?1能增大到它右面比它大的那一系列数中最小的那个数,即y=3,故此时21543的下一个排列应该变为23xxx,显然xxx应由小到大排列,于是最终找到比21543大但字典顺序尽量小的23145.

先来定义升序,即相邻两个位置ai < a i+1, ai称作该升序的首位。

由上述例子可以得出next_permutation算法的具体步骤(二找、一交换、一翻转)如下。

1、找到排列中最后(最右)一个升序的首位位置i, x= ai

2、找到排列中第i位右边最后一个比ai大的位置j,y = aj

3、交换x和y

4、把第i+1位到最后的部分翻转(执行此步骤前,因为第i位是最后一个升序的位置,所以从i+1到n一定是降序排列的,而执行此步骤后,从i+1到n变成升序排列)

参考代码:

void CalcAllPermutation (char * perm, int num)
{
	int i;
	// 1、找到排列中最后一个升序的首位位置i,x = ai
	for (i = num -2; (i >= 0) && (perm[i] >= perm(i+1)); --i)
	{
		;
	}
	// 已经找到所有排列
	if (i < 0)
	{
		return false;
	}
	int k;
	// 2、找到排列中第i位右边最后一个比ai大的位置j,y = aj
	for (k = num - 1; (k > i) && (perm[k] <= perm[i]); --k)
	{
		;
	}
	// 3、交换x和y
	swap(perm[i], perm[k]);
	// 4、把第i+1位到最后的部分翻转
	reverse(perm + i + 1, perm + num);

	return true;
}

然后在主函数里循环判断和调用CalcAllPermutation函数即可按照字典顺序输出所有排列。由于全排列共有n!种排列情况,所以不论是递归法还是字典序排列,时间复杂度都为O(n!)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值