字符串的排列与组合

全排列

所谓全排列,就是打印出字符串中所有字符的所有排列。例如输入字符串abc,则打印出 a、b、c 所能排列出来的所有字符串 abcacbbacbcacab 和 cba 。

一般最先想到的方法是暴力循环法,即对于每一位,遍历集合中可能的元素,如果在这一位之前出现过了该元素,跳过该元素。例如对于abc,第一位可以是 a 或 b 或 c 。当第一位为 a 时,第二位再遍历集合,发现 a 不行,因为前面已经出现 a 了,而 b 和 c 可以。当第二位为 b 时 , 再遍历集合,发现 a 和 b 都不行,c 可以。可以用递归或循环来实现,但是复杂度为 O(nn) 。有没有更优雅的解法呢。

首先考虑baccba这二个字符串是如何得出的。显然这二个都是abc中的 a 与后面两字符交换得到的。然后可以将abc的第二个字符和第三个字符交换得到acb。同理可以根据baccba来得bcacab

因此可以知道 全排列就是从第一个数字起每个数分别与它后面的数字交换,也可以得出这种解法每次得到的结果都是正确结果,所以复杂度为 O(n!)。找到这个规律后,递归的代码就很容易写出来了:

#include<stdio.h>
#include<string>
//交换两个字符
void Swap(char *a ,char *b)
{
	char temp = *a;
	*a = *b;
	*b = temp;
}
//递归全排列,start 为全排列开始的下标, length 为str数组的长度
void AllRange(char* str,int start,int length)
{
	if(start == length-1)
	{
		printf("%s\n",str);
	}
	else
	{
		for(int i=start;i<=length-1;i++)	
		{	//从下标为start的数开始,分别与它后面的数字交换
			Swap(&str[start],&str[i]); 
			AllRange(str,start+1,length);
			Swap(&str[start],&str[i]); 
		}
	}
}
void Permutation(char* str)
{
	if(str == NULL)
		return;
	AllRange(str,0,strlen(str));
}
void main()
{
	char str[] = "abc";
	Permutation(str);
}

去重的全排列

为了得到不一样的排列,可能我们最先想到的方法是当遇到和自己相同的就不交换了。如果我们输入的是abb,那么第一个字符与后面的交换后得到 babbba。然后abb中,第二个字符和第三个就不用交换了。但是对于bab,它的第二个字符和第三个是不同的,交换后得到bba,和之前的重复了。因此,这种方法不行。

因为abb能得到babbba,而bab又能得到bba,那我们能不能第一个bba不求呢? 我们有了这种思路,第一个字符a与第二个字符b交换得到bab,然后考虑第一个字符a与第三个字符b交换,此时由于第三个字符等于第二个字符,所以它们不再交换。再考虑bab,它的第二个与第三个字符交换可以得到bba。此时全排列生成完毕,即abbbabbba三个。

这样我们也得到了在全排列中去掉重复的规则:去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。下面给出完整代码:

#include<stdio.h>
#include<string>
//交换两个字符
void Swap(char *a ,char *b)
{
	char temp = *a;
	*a = *b;
	*b = temp;
}
//在 str 数组中,[start,end) 中是否有与 str[end] 元素相同的
bool IsSwap(char* str,int start,int end)
{
	for(;start<end;start++)
	{
		if(str[start] == str[end])
			return false;
	}
	return true;
}
//递归去重全排列,start 为全排列开始的下标, length 为str数组的长度
void AllRange2(char* str,int start,int length)
{
	if(start == length-1)
	{
		printf("%s\n",str);
	}
	else
	{
		for(int i=start;i<=length-1;i++)
		{
			if(IsSwap(str,start,i))
			{
				Swap(&str[start],&str[i]); 
				AllRange2(str,start+1,length);
				Swap(&str[start],&str[i]); 
			}
		}
	}
}
void Permutation(char* str)
{
	if(str == NULL)
		return;
	AllRange2(str,0,strlen(str));
}
void main()
{
	char str[] = "abb";
	Permutation(str);
}

组合

把问题简化为长度为n的字符串中求m个字符串的组合。
我可以从第一个字符开始,有两种情况:
1,含有这个字符,然后求剩下m-1个字符串的组合;
2,不含有这个字符,然后求剩下m个字符串的组合;
a+a(fun(bc))+fun(bc)
fun(bc):
b+bfun(c)+fun(c)
也就是:
b,bc,c
so:
a,a(b,bc,c),(b,bc,c)

和很多字符串的题目一样,我们使用递归的方法:
首先是驱动函数:

#include <iostream>
#include <vector>

void combination(char* str, int number, std::vector<char>& vec);

void combinate(char* str)
{
    if (!str)
        return;
    size_t len = strlen(str);

    std::vector<char> str_vec;

    for (int i = 1; i <= len; i++)
    {
        combination(str, i, str_vec);
    }
}

首先我们检测输入是否合法,然后我们从第一个字符串开始,得到关于当前这个字符和后面字符串(看成一个整体)的序列,而后面所有字符的情况只需要递归的调用就好了

void combination(char* str, int number, std::vector<char>& vec)
{
    if (number == 0)
    {
        auto beg = vec.begin();
        for (beg; beg!=vec.end(); beg++)
            std::cout << *beg;
        std::cout << std::endl;
    }
    if (*str != '\n')
        return;

    vec.push_back(*str);
    combination(str+1, number - 1, vec);

    vec.pop_back();
    combination(str + 1, number, vec);

}

我们的参数列表包括字符串的指针,当前字符的位置number,还有存储在vector里面的字符(等待打印)。

这个函数就是按照前面的思路写的:

1,含有这个字符,然后求剩下m-1个字符串的组合;
2,不含有这个字符,然后求剩下m个字符串的组合;

情况1对应:
vec.push_back(*str); combination(str+1, number - 1, vec);
情况2:
vec.pop_back(); combination(str + 1, number, vec);
先把当前字符删除,然后打印





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值