您的位置:首页 > 其它

字符串的排列与组合

2018-02-23 18:58 176 查看

全排列

所谓全排列,就是打印出字符串中所有字符的所有排列。例如输入字符串
abc
,则打印出 a、b、c 所能排列出来的所有字符串 
abc
acb
bac
bca
cab
 和 
cba
 。一般最先想到的方法是暴力循环法,即对于每一位,遍历集合中可能的元素,如果在这一位之前出现过了该元素,跳过该元素。例如对于
abc
,第一位可以是 a 或 b 或 c 。当第一位为 a 时,第二位再遍历集合,发现 a 不行,因为前面已经出现 a 了,而 b 和 c 可以。当第二位为 b 时 , 再遍历集合,发现 a 和 b 都不行,c 可以。可以用递归或循环来实现,但是复杂度为 O(nn) 。有没有更优雅的解法呢。首先考虑
bac
cba
这二个字符串是如何得出的。显然这二个都是
abc
中的 a 与后面两字符交换得到的。然后可以将
abc
的第二个字符和第三个字符交换得到
acb
。同理可以根据
bac
cba
来得
bca
cab
。因此可以知道 全排列就是从第一个数字起每个数分别与它后面的数字交换,也可以得出这种解法每次得到的结果都是正确结果,所以复杂度为 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
,那么第一个字符与后面的交换后得到 
bab
bba
。然后
abb
中,第二个字符和第三个就不用交换了。但是对于
bab
,它的第二个字符和第三个是不同的,交换后得到
bba
,和之前的重复了。因此,这种方法不行。因为
abb
能得到
bab
bba
,而
bab
又能得到
bba
,那我们能不能第一个
bba
不求呢? 我们有了这种思路,第一个字符
a
与第二个字符
b
交换得到
bab
,然后考虑第一个字符
a
与第三个字符
b
交换,此时由于第三个字符等于第二个字符,所以它们不再交换。再考虑
bab
,它的第二个与第三个字符交换可以得到
bba
。此时全排列生成完毕,即
abb
bab
bba
三个。这样我们也得到了在全排列中去掉重复的规则:去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第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);

先把当前字符删除,然后打印
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: