问题:有一个长度为 nLength 的字符串 S,不考虑 S 里面有相同的字符,将 S 中出现的字符排列成长度为 nLength 的字符串,打印出所有的排列字符串(包括原串)。
如,给定 abc ,则要输出 abc,acb,bac,bca,cab,cba。
为了得到所有排列,我们采用首字符交换法,即,把后面的字符逐个与首字符交换,再把后面的字符串当作一个子问题,于是可以用递归解决,如图1所示。
图1 递归解法
给出源码:
/*
输入字符串:inputWord
每一次递归到最深层时组合形成的完整字符串:outputWord
要组合的字符串的开始/结束位置:begin(inclusive) / end(exclusive)
*/
void combineAndShow(char *inputWord,char *outputWord,int begin,int end)
{
const char FIRST_CHAR = *(inputWord + begin);
const char LAST_OUTPUT_CHAR = *(outputWord + begin);
char &firstCh = *(inputWord + begin);
char &lastOutPutCh = *(outputWord + begin);
*(outputWord + begin + 1) = '\0';
if(begin == end - 1)
{
printf("%s\r\n",outputWord);
return;
}
//原首字母不动 // 将 i = beging 的情况单独处理是因为此时 firstCh 和 currentCh 是一个变量的两个引用
// 使用 a ^= b;b ^= a;a ^= b;的方法用于两边是同一个变量时会有错。
// 如 int a = 1; a ^= a; a ^= a; a ^= a; 这样做会将 a 置为 0;
lastOutPutCh = firstCh;
combineAndShow(inputWord,outputWord,begin + 1,end);
lastOutPutCh = LAST_OUTPUT_CHAR;
*(outputWord + begin + 1) = '\0';
for (int i = begin + 1;i != end - 1; ++ i)
{
char ¤tCh = *(inputWord + i);
lastOutPutCh = currentCh;
//将 currentCh 交换到字符串的首位置
firstCh ^= currentCh;
currentCh ^= firstCh;
firstCh ^= currentCh;
combineAndShow(inputWord,outputWord,begin + 1,end);
//之后要交换回来
lastOutPutCh = LAST_OUTPUT_CHAR;
*(outputWord + begin + 1) = '\0';
firstCh ^= currentCh;
currentCh ^= firstCh;
firstCh ^= currentCh;
}
}
int main()
{
freopen("C:\\outPut.txt","w",stdout);
char str [] = {"12345678"}; //这里不能用字符串常量 : char *str = "12345678";
//因为后面的算法会更改这个字符串,所以要分配空间
char outPut[9] = {0};
combineAndShow(str,outPut,0,9);
return 0;
}
而一般情况下,递归的复杂度比动态规划复杂度高不少,因为,递归调用有比较大的时间耗费,而且也会有很深的函数栈,耗费不少的内存空间。而迭代没有函数递归调用的负担,它保存 i - 1 次计算结果并直接用于第 i 次的计算。 所以迭代的效率一般高于递归。我们把这个解决办法换为迭代的。
为此,另外先写了一篇文章,《算法之递归,迭代,动态规划,分冶》里面归纳了递归与迭代的本质不同,既然知道不同,就知道如何去转换了。在转换为迭代的过程中,我们采用最简单的转换方法,从底向上迭代,将第 i- 1 轮的计算结果(设为 F(i-1))缓存下来用于本轮(第 i 轮)构成结果 F(i)。
定义 i 为字符串下标,设字符串为 S,S[i] 表示取 S 的第 i 个字符。
F(0) 表示字符串为 S[0] 组合的结果,
F(1) 表示字符串为 S[0],S[1] 组合的结果,
F(2) 表示字符串为 S[0],S[1],S[2]组合的结果,
F(3) 表示字符串为 S[0],S[1],S[2],S[3] 组合的结果,
每一个 F 都是一个集合(也可以称为字符串数组)。
模拟由 F(2) 得到 F(3):
1.对于 F(2) 中的每一个字符串 T 有以下步骤2:
2.将 S[3] 追加到 T 的末尾。新得到的 T 为 T'。对 T' 有步骤3
3.遍历 T' 的每一个字符,将它与 T' 最末尾的字符交换。每次交换得到一个新字符串加入 F(3)
经过这三步,便能求出 F(3)。
其实这种转换是不节实际的,因为这需要很大的空间来存放结果。