1、问题描述
给定一组不包含重复元素的集合 { s 1 , s 2 , … s n } \{s_1,s_2,…s_n\} {s1,s2,…sn} ,求这个集合中所有元素能构成的全排列。
2、问题分析
构造一个全排列相当于向 n 个空位不放回地填放集合中的元素,第
i
i
i 个位置有
n
−
i
+
1
n - i + 1
n−i+1 种选择。所以问题的解空间是一个树结构(如下图所示)。每一个从根节点到叶节点的路径即为一个全排列。因此可以通过遍历树的叶节点,记录从根节点到叶节点的每条路径,得到全排列。
通过回溯法对解空间的树结构进行遍历,伪代码如下:
bool flag[len] /* 标记某个元素是否被使用 */
type buff[len] /* 记录数据 */
/* backtrack: 回溯函数
1. len:集合 {s_1,s_2,…s_n} 中元素的个数,为 n
2. depth:当前遍历到的树的深度,root 为 0,最大为 n
3. sequence:当前的路径
*/
void backtrack(len, depth,sequence)
{
if len = depth
then save(sequence)
return
for i <- 0 to len do
if flag[i] = false /* 第 i 个元素还未被使用 */
/* 选择一个分支 */
flag[i] = true
sequence[depth] = buff[i]
/* 递归遍历 */
backtrack(len, depth + 1, sequence)
/* 回溯 */
flag[i] = flase
sequence[depth] = null
end
}
分析过程发现,如果初始时 [ s 1 , s 2 , … , s n ] [s_1, s_2, …,s_n] [s1,s2,…,sn] 按字典序排列,则全排列的保存的序列也是按字典序排列的。
3、代码示例
以字符串的全排列为例(不包含重复字符)字符串最长为 6。
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
bool flag[6];
char buff[6];
int len;
void backtrack(vector<vector<char>> &res, vector<char> &seq, int idx)
{
if (idx == len)
{
res.push_back(seq);
return;
}
for (size_t i = 0; i < len; i++)
{
if (flag[i] == false)
{
// 尝试路径
flag[i] = true;
seq.push_back(buff[i]);
// 递归
backtrack(res, seq, idx + 1);
// 恢复现场
seq.pop_back();
flag[i] = false;
}
}
}
int main(int argc, char const *argv[])
{
scanf("%s", buff);
len = strlen(buff);
memset(flag, false, sizeof(flag));
vector<vector<char>> res; // 二维嵌套 vector 保存所有的全排列
vector<char> seq; // 记录单个全排列
backtrack(res, seq, 0);
/* 打印输出 */
for (size_t i = 0; i < res.size(); i++)
{
for (size_t j = 0; j < res[i].size(); j++)
{
cout << res[i][j];
}
cout << endl;
}
return 0;
}