回溯算法
通過一些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();
}
}
}
}