使用回溯法求解子集(幂集)问题
有一个含n个整数的数组a,所有元素均不相同,设计一个算法求其所有子集(幂集)。
例如,a[]={1,2,3},所有子集是:{},{3},{2},{2,3},{1},{1,3},{1,2},{1,2,3}(输出顺序无关)。
求解过程和结果如下图所示:
1.
递归解决组合问题.
节点信息是集合中存在的元素.也就是我们选择的元素.
2.
n个整数的数组a,我们需要选择n次,因此i表示当前节点的位置.
递归的出口试i==n的时候.
3.
递归函数只需要做两件事情,第一件事是选择,第二件事是不选择.
只需要维护path信息即可.
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
// 定义一个全局的整数数组
vector<int> arr = {1, 2, 3};
// 定义一个全局的路径向量,用于存储当前路径
vector<int> path;
// 深度优先搜索函数,用于生成所有子集
void dfs(int i) {
// 如果当前索引等于数组大小,说明已经到达叶子节点
if (i == arr.size()) {
cout << "{ ";
// 输出当前路径中的元素,即一个子集
for (const auto& x : path) {
cout << x << " ";
}
cout << "}" << endl;
return;
}
// 选择当前元素
path.push_back(arr[i]);
// 递归调用,处理下一个元素
dfs(i + 1);
// 撤销选择,回溯到上一个状态
path.pop_back();
// 不选择当前元素
dfs(i + 1);
}
// 初始化函数(在这个程序中没有实际内容)
void init() {}
// 主解决函数,调用深度优先搜索函数
void solve() {
dfs(0);
}
// 主函数,程序的入口
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
init(); // 初始化
solve(); // 解决问题
}
使用回溯法求解“和”问题
设计一个算法在1,2,…,9(顺序不能变)数字之间插入+或-或什么都不插入,使得计算结果总是100的程序,并输出所有的可能性。
例如:1+2+34–5+67–8+9=100。
1.
1~9中间有8个可以选择的符号,所以我们需要dfs8次,每一次对一个位置进行选择.
选择+或者-或者空.
2.
递归的节点信息,i表示当前的节点位置.
sum表示当前累加和.
lastnums表示最后一个进来的数字.
path表示当前表达式.
3.
选择空的时候,维护path只需要尾插当前数字.
维护sum只需要剪掉最后一个进来的数字,然后将新数字添加进来.
#include <bits/stdc++.h>
using namespace std;
// 初始化表达式路径为 "1"
string path = "1";
// 深度优先搜索函数
void dfs(int i, int sum, int lastnums) {
// 如果当前索引等于10,说明已经处理完所有数字
if (i == 10) {
// 如果当前累计和等于100,输出表达式
if (sum == 100) {
cout << path << "=100" << endl;
}
return;
}
// 保存当前路径
string oldpath = path;
// 选择加号
path += "+" + to_string(i); // 在路径中添加当前数字和加号
dfs(i + 1, sum + i, i); // 递归处理下一个数字,更新累计和和最后一个数字
path = oldpath; // 恢复路径
// 选择减号
path += "-" + to_string(i); // 在路径中添加当前数字和减号
dfs(i + 1, sum - i, -i); // 递归处理下一个数字,更新累计和和最后一个数字
path = oldpath; // 恢复路径
// 选择不插入符号,直接连接数字
path += to_string(i); // 在路径中直接添加当前数字
int new_lastnums = lastnums > 0 ? lastnums * 10 + i : lastnums * 10 - i; // 更新最后一个数字
dfs(i + 1, sum - lastnums + new_lastnums, new_lastnums); // 递归处理下一个数字,更新累计和和最后一个数字
path = oldpath; // 恢复路径
}
// 初始化函数(在这个程序中没有实际内容)
void init() {}
// 主解决函数,调用深度优先搜索函数
void solve() {
dfs(2, 1, 1); // 从数字2开始递归,初始累加和为1,初始最后一个数字为1
}
// 主函数,程序的入口
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
init(); // 初始化
solve(); // 解决问题
return 0;
}
使用回溯法求解全排列问题
有一个含n个整数的数组a,所有元素均不相同,求其所有元素的全排列。
例如,a[]={1,2,3},得到结果是(1,2,3)、(1,3,2)、(2,3,1)、(2,1,3)、(3,1,2)、(3,2,1)
求解过程和结果如下图所示:
1.
全排列问题,用递归解决.n个位置,考虑每一个位置的每一种情况.
例如第一个问题可以选择哪些数字,第二个位置可以选择哪一些数字.以此类推.
2.
当前节点信息,i表示当前处于哪一个位置.
visited存储已经使用过的数字.
ret表示全排列序列.
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
// 用于存储当前排列的字符串
string ret = "";
// 定义常量n,表示数组的大小
const int n = 3;
// 用于标记每个数字是否已经被使用过
vector<bool> visited;
// 深度优先搜索函数
void dfs(int i) {
// 如果当前索引大于n,说明已经生成了一个完整的排列
if (i > n) {
cout << "{ ";
// 输出当前排列
for (const auto& num : ret) {
cout << num << " ";
}
cout << "}" << endl;
return;
}
// 遍历所有可能的数字
for (int j = 1; j <= n; j++) {
// 如果数字j没有被使用过
if (!visited[j]) {
visited[j] = true; // 标记数字j为已使用
ret += to_string(j); // 将数字j添加到当前排列
dfs(i + 1); // 递归处理下一个位置
visited[j] = false; // 回溯,标记数字j为未使用
ret.pop_back(); // 回溯,移除最后一个字符
}
}
}
// 初始化函数,初始化visited数组
void init() {
visited.assign(n + 1, false); // 初始化visited数组,大小为n+1,并设置为false
}
// 主解决函数,调用深度优先搜索函数
void solve() {
dfs(1); // 从位置1开始生成排列
}
// 主函数,程序的入口
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
init(); // 初始化
solve(); // 解决问题
}
使用回溯法求解0/1背包问题(同时进行左剪枝和右剪枝改进)
有n个重量分别为{w1,w2,…,wn}的物品,它们的价值分别为{v1,v2,…,vn},给定一个容量为W的背包。设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且具有最大的价值。
并对下表所示的4个物品求出W=6时的所有解和最佳解。
物品编号 | 重量 | 价值 |
1 | 5 | 4 |
2 | 3 | 4 |
3 | 2 | 3 |
4 | 1 | 1 |
1.
递归函数,节点信息i表示当前考虑选择或者不选择的物品下标.
w表示当前背包里面的重量.
v表示当前背包里面的价值.
path表示当前背包选择的物品.
2.
剪枝操作,如果当前背包的w重量大于最大重量,返回.
如果当前位置i位置到结尾的所有的物品的价值加上当前价值都小于等于ret的价值,此时不可能使得ret变大,所以直接返回.
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
// 物品结构体,包含重量和价值
struct node {
int weight = 0;
int value = 0;
};
// 结果结构体,包含路径、总重量和总价值
struct node1 {
string path = "";
int weight = 0;
int value = 0;
};
// 物品列表,包含每个物品的重量和价值
vector<node> cost = { {5, 4}, {3, 4}, {2, 3}, {1, 1} };
const int n = 4; // 物品数量
node1 ret; // 最优结果
string path; // 当前路径
int count1 = 1; // 方案计数
const int maxWeight = 6; // 背包最大容量
vector<int> sum_value; // 从当前位置到末尾的价值总和
// 深度优先搜索函数,不使用剪枝
void dfs(int i, int w, int v) {
// 如果当前索引等于物品数量,表示已经处理完所有物品
if (i == n) {
path += "}"; // 在路径末尾添加右花括号
// 输出当前的路径、总重量和总价值
printf("%-10lld%-20s%-10lld%-10lld%-10s\n", count1++, path.c_str(), w, v, w <= maxWeight ? "能" : "否");
// 如果当前重量不超过最大重量且当前价值大于最优结果的价值,更新最优结果
if (w <= maxWeight && ret.value < v) {
ret.path = path;
ret.value = v;
ret.weight = w;
}
path.pop_back(); // 回溯,移除右花括号
return;
}
string oldpath = path; // 记录当前路径
// 选择当前物品
path += to_string(i + 1) + " "; // 在路径中添加当前物品的编号
dfs(i + 1, w + cost[i].weight, v + cost[i].value); // 递归处理下一个物品
path = oldpath; // 回溯,恢复路径
// 不选择当前物品
dfs(i + 1, w, v); // 递归处理下一个物品
}
// 深度优先搜索函数,使用剪枝
void dfs1(int i, int w, int v) {
// 剪枝条件1:当前重量超过背包容量
if (w > maxWeight) return;
// 剪枝条件2:当前价值加上剩余物品的最大价值不超过当前最优解
if (v + (i < n ? sum_value[i] : 0) <= ret.value) return;
// 如果当前索引等于物品数量,表示已经处理完所有物品
if (i == n) {
path += "}"; // 在路径末尾添加右花括号
// 输出当前的路径、总重量和总价值
printf("%-10lld%-20s%-10lld%-10lld%-10s\n", count1++, path.c_str(), w, v, w <= maxWeight ? "能" : "否");
// 如果当前重量不超过最大重量且当前价值大于最优结果的价值,更新最优结果
if (w <= maxWeight && ret.value < v) {
ret.path = path;
ret.value = v;
ret.weight = w;
}
path.pop_back(); // 回溯,移除右花括号
return;
}
string oldpath = path; // 记录当前路径
// 选择当前物品
path += to_string(i + 1) + " "; // 在路径中添加当前物品的编号
dfs1(i + 1, w + cost[i].weight, v + cost[i].value); // 递归处理下一个物品
path = oldpath; // 回溯,恢复路径
// 不选择当前物品
dfs1(i + 1, w, v); // 递归处理下一个物品
}
// 初始化函数,计算每个位置到末尾的价值总和
void init() {
sum_value.assign(n, 0); // 初始化sum_value数组
for (int i = n - 1; i >= 0; i--) {
// 计算当前位置到末尾的总价值
sum_value[i] = (i + 1 >= n ? 0 : sum_value[i + 1]) + cost[i].value;
}
}
// 主解决函数,调用深度优先搜索函数
void solve() {
cout << "dfs1:" << endl;
path += "{ "; // 初始化路径
cout << "0/1背包的求解方案" << endl;
// 输出标题信息
printf("%-10s%-20s%-10s%-10s%-10s\n", "序号", "选中物品", "总重量", "总价值", "能否装入");
dfs(0, 0, 0); // 调用不剪枝的dfs函数
// 输出不剪枝的最佳方案
printf("最佳方案为 选中物品:%s,总重量:%lld,总价值:%lld\n\n", ret.path.c_str(), ret.weight, ret.value);
ret = node1(); // 重置最优结果
count1 = 1; // 重置计数
cout << "dfs2:" << endl;
path = "{ "; // 重新初始化路径
cout << "0/1背包的求解方案" << endl;
// 输出标题信息
printf("%-10s%-20s%-10s%-10s%-10s\n", "序号", "选中物品", "总重量", "总价值", "能否装入");
dfs1(0, 0, 0); // 调用剪枝的dfs1函数
// 输出剪枝的最佳方案
printf("最佳方案为 选中物品:%s,总重量:%lld,总价值:%lld\n", ret.path.c_str(), ret.weight, ret.value);
}
// 主函数,程序的入口
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 提高输入输出效率
init(); // 初始化
solve(); // 解决问题
}
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!