求字符串的排列和组合是递归很典型的应用,是很好的考查对递归理解程度的一类问题。下面的求字符串的排列、组合的算法全部用递归实现。
1. 字符串的排列
(1) 描述
输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串 abc,则输出由字符 a、b、c 所能排列出来的所有字符串 abc、acb、bac、bca、cab 和 cba。
(2) 字符串中没有重复字符
先不考虑字符串中的重复字符(如果有的话),分析完后再考虑有重复字符的情况就比较简单了。
以三个字符 abc 为例来分析一下求字符串排列的过程。
首先固定第一个字符 a,求后面两个字符bc 的排列。当两个字符 bc 的排列求好之后,把第一个字符 a 和后面的 b 交换,得到 bac,接着固定第一个字符 b,求后面两个字符 ac 的排列。现在是把 c 放到第一位置的时候了。注意,前面已经把
原先的第一个字符 a 和后面的 b 做了交换,为了保证这次 c 仍然是和原先处在第一位置的 a 交换,在拿 c 和第一个字符交换之前,先要把 b 和 a 交换回来。在交换 b 和 a 之后,再拿 c 和处在第一位置的 a 进行交换,得到 cba。再次固定第一个字符 c,求后面两个字符 b、a 的排列。
求三个字符的排列的过程就是这样了,固定第一个字符之后求后面两个字符的排列,就是典型的递归思路了。
代码如下:
void stringPermutation(char *str) { //call permutation
permutation(str, str);
// permutation2(str, str)
}
void permutation(char *str, char *start) //the main algorithm soving string permutaion
{
if (! *start) //到达字符串的末尾,说明一个排列已产生
printf("%s\n", str);
else {
char *fromStart, temp;
for (fromStart = start; *fromStart; fromStart++)
{
/**
* exchange *fromStart with *start, fix the *start,
* permutate from *(start + 1) to the end.
* */
temp = *start;
*start = *fromStart;
*fromStart = temp;
permutation(str, start + 1);
temp = *start;
*start = *fromStart;
*fromStart = temp;
}// for
}// else
}
测试如下:
#include <stdio.h>
void stringPermutation(char *str);
void permutation(char *str, char *start);
int main(void)
{
char str[] = "abc";
stringPermutation(str);
return 0;
}
输出:
(3) 字符串中有重复字符
万一字符串中有重复字符,上面的算法打印出来的排列就有重复的了。下面来分析一下怎么办吧。
由于全排列就是从第一个字符起每个字符分别与它后面的字符交换。先尝试个这样的思路——如果一个字符与后面的字符相同那么这两个数就不交换。如abb,第一个数与后面交换得bab、bba。然后abb中第二个字符b就不用与第三个字符b交换了,但对bab,它第二个字符a与第三个字符b是不相同的,交换之后得到bba。与由abb中第一个字符与第三个字符交换所得的bba重复了。所以这个思路是行不同的。
换种思路,对abb,第一个字符a与第二个字符b交换得到bab,然后考虑第一个字符a与第三个字符b交换,此时由于第三个字符等于第二个字符,而第一个字符a已与第二个字符b交换过了,所以第一个字符a不再与第三个字符b交换。再考虑bab,它的第二个字符a与第三个字符交换得到bba。全排列生成完毕。
这样就得到了在全排列中去掉重复的规则——去重的全排列就是从第一个字符起每个字符分别与它后面非重复出现的字符交换。
代码如下
void permutation2(char *str, char *start)
{
if (! *start)
printf("%s\n", str);
else {
char *fromStart, temp;
for (fromStart = start; *fromStart; fromStart++)
{
if (canSwap(start, fromStart)) { //判断字符*fromStart是否是重复出现的,
temp = *start; //即字符*start是否已与字符*fromStart交换过
*start = *fromStart;
*fromStart = temp;
permutation2(str, start + 1);
temp = *start;
*start = *fromStart;
*fromStart = temp;
} //if
}// for
}// else
}
判断函数canSwap如下:
int canSwap(char *start, char *end)
{
for (;start < end;start++) {
if (*start == *end)
return 0; //can not swap
}
return 1; //can swap
}
测试如下:(str[] = "abb")
2. 字符串的组合
(1) 字符串中没有重复字符
先考虑字符串abc的所有组合:
一个字符时:a、b、c
两个字符时:ab、ac、bc
三个字符时:abc
在长度为n的字符串中求m个字符的组合时,先从头扫描字符串的第一个字符。 针对第一个字符,有两种选择:
1) 第一是把这个字符放到组合中去, 接下来需要在剩下的n-1个字符中选取m-1个字符;
2) 第二是不把这个字符放到组合中去,接下来需要在剩下的n-1个字符中选择m个字符。
这两种选择都很容易用递归实现,递归思路出来了。
代码如下:
/**
* //global defination
* #define N 50
* typedef struct Vector {
* char stack[N];
* int top;
* }Vector;
*
* */
void stringCombination(char *str)
{
int i;
Vector vector;
vector.top = 0;
for (i = 1; i <= strlen(str); i++) {
combination(str, i, &vector);
}
}
void combination(char *str, int remain, Vector *vector)
{
if (remain == 0) { //还需要选择的字符个数,remain = 0时说明包含i个字符的组合已经产生
int i;
for (i = 0; i < vector->top; i++)
printf("%c", vector->stack[i]);
printf("\n");
} else {
if (! *str)
return;
vector->stack[vector->top++] = *str; //选择当前字符
combination(str + 1, remain - 1, vector);
vector->top--; //不选当前字符
combination(str + 1, remain, vector);
}
}
测试如下:(str[] = "abc")
(2) 字符串中有重复字符
时间不早了,下次补上