回溯算法
通过一些leetcode上面的题目, 总结出以下类型的题目:
- 子集生成:
- 非可重集的子集
- 可重集的子集
- 全排列
- 非可重合的全排列
- 可重集的全排列.
- next_permutation()
- N-Queen
非可重集的子集生成:
- 解答树如下:
如果要生成子集的集合为 [1, 2, 3]. 其解答树如下:
- 定序技巧:
将已经选择元素的下标通过递归函数的一个参数传递给下子节点就行了, 子节点选择的元素只能在父节点选择元素的位置之后的位置进行选择。
- 代码板子
// nums 选择元素的集合
// cur 当前选择是第几个元素
// t 存放已经选择的元素
// pre 上一个节点选择的元素的编号
void subset(vector<int> &nums, int cur, vector<int> &t, int pre)
{
// 直接输出当前节点代表的子集
for(int i = 0; i != t.size(); ++i) cout << t[i] << " ";
cout << endl;
int s = pre == -1 ? 0 : pre + 1; /* 定序*/
for(int i = s; i < nums.size(); ++i) /* 从s右边的元素当中选择*/
{
t.push_back(nums[i]);
subset(nums, cur + 1, t, i);
}
}
可重集的子集的子集生成.
先将数组排好序, 对于解答树中一个父亲节点选择出来的所有子节点当中, 相邻的两个节点的值不能相同。
- 解答树: [1, 2, 2, 3]
- 代码板子
sort(nums.begin(), nums.end());
void subset(vector<int> &nums, int cur, vector<int> &t, int pre)
{
// 直接输出当前节点代表的子集
for(int i = 0; i != t.size(); ++i) cout << t[i] << " ";
cout << endl;
int s = pre == -1 ? 0 : pre + 1; /* 定序*/
for(int i = s; i < nums.size(); ++i) /* 从s右边的元素当中选择*/
{
//比较当前待选择的节点和上一个兄弟节点的值是否相同
if(i != s && nums[i] == nums[i - 1])) continue;
t.push_back(nums[i]);
subset(nums, cur + 1, t, i);
}
}
非可重集的全排列
选择一个元素, 再从剩余元素当中选择下一个元素就行啦。
- 板子:
void permutation(vector<int> &nums, int cur, vector<int> &t)
{
if(cur >= nums.size())
//得到一个全排列t;
else
{
for(int i = 0; i != nums.size(); ++i)
{
//已经选择的元素不能再选择了。
bool flag = false;
for(int j = 0; j != t.size(); ++j) if(nums[i] == t[j]) {flag = true; break;}
if(flag) continue;
//选择当前元素
t.push_back(nums[i]);
permutation(nums, cur + 1, t, ans);
t.pop_back();
}
}
}
可重集的全排列
- 板子:
void permutation(vector<int> &nums, int cur, vector<int> &t)
{
if(cur >= nums.size())
//得到一个全排列t;
else
{
for(int i = 0; i != nums.size(); ++i)
{
if(i && P[i] == P[i - 1]) continue; // 等价上述子集生成当中的描述
int c1 = 0, c2 = 0; // 统计已经选择了的值为P[i] 的元素 和 其总数, 得出是否能再选择。
for(int j = 0; j < cur; ++j) if(t[j] == P[i]) c1++;
for(int j = 0; j < n; ++j) if(P[j] == P[i]) c2++;
if(c1 < c2)
{
// 选择当前元素
t.push_back(nums[i]);
permutation(nums, cur + 1, t, ans);
t.pop_back();
}
}
}
}