排列数简介
许多文字游戏和谜题需要重新排列一组字母来形成一个单词。因此,如果想编写一个拼字游戏程序,设置一个可以产生一组特定的组合的单词的所有可能的排列是有用的。 在文字游戏中,这样的安排通常被称为变位词(anagrams)。 在数学中,它们被称为排列(permutations)。
假设你想写一个函数:
set<string> generatePermutations(string str);
用来返回一个包含str所有排列的集合。举个例子,如果你调用:
generatePermutations("ABC");
函数应该返回的是这样的:
{ "ABC", "ACB", "BAC", "BCA", "CAB", "CBA" }
您将如何执行generatePermutations功能? 如果我们仅仅限于迭代控制结构,找到适用于任何长度的字符串的一般解决方案是很困难的。但是如果我们递归地思考问题,就会产生一个相对简单的解决方案。与其他的递归程序一样,解决过程的难点在于如何将原始问题分解成相同问题的更简单的实例。在这种情况下,为了生成字符串的所有排列,我们需要发现如何能够生成更短字符串的所有排列,这将会有助于我们查找解决方案。
在看完我的解决方案之前,请先停下来思考这个问题几分钟。当你第一次学习递归时,很容易看懂一个递归的解决方案,并相信你可以自己生成它。不过你得先尝试一下,因为很难知道你是否会提出必要的递归见解。
寻找递归规律(Finding the recursive insight)
为了给自己更多的对于这个问题的感觉,我们可以考虑一个具体的情况。假设你想生成一个五个字符的字符串的所有排列,如“ABCDE”。 在的解决方案中,我们可以应用leap of faith,以生成任何较短字符串的所有排列。只要假设递归调用工作并且完成。这里,关键的问题是如何能够排列较短的字符串以帮助我们解决排列原来的五个字符的问题?
如果你专注于将五个字符的问题分解成一些涉及四个字符串的问题的实例,你很快会发现五个字符的字符串“ABCDE”的排列由以下字符串组成:
- 字符A,后面跟着的是字符串“BCDE”的所有排列(The character ‘A’ followed by every possible permutation of “BCDE”)
- 字符B,后面跟着的是字符串“ACDE”的所有排列(The character ‘B’ followed by every possible permutation of “ACDE”)
- 。。。。。
…等等都是一样的。
更一般地说,我们可以通过依次选择每个字符来构造长度为n的字符串的所有排列的集合,然后,对于那些n个可能的第一个字符中的每一个,将所选字符连接到剩余的每个可能排列的前面 n - 1 个字符。产生n-1的所有排列的问题是相同问题的较小实例,因此可以递归地求解。
跟以前一样,我们还需要定义一个simple case。一种可能性是检查字符串是否包含单个字符。计算单个字符串的所有排列很容易,因为只有一个可能的排序。然而,在字符串处理中,简单情况下的最佳选择很少是一个字符的字符串,因为实际上还有一个更简单的替代方法:空字符串根本不包含任何字符。就像单字符串只有一个排序一样,只能写一个空字符串的一种方法。 如果调用generatePermutations(“”),则应该返回一个包含单个元素的集合,就是空字符串。
要实现这个功能,我们就要有一个循环,foreach。那么在C++怎么实现foreach呢?看这个:
C++抽象编程——递归策略(3)——foreach语句的简单实现
C++抽象编程——递归简介(3)——生成排列数(2)