1.算法概述
子集树是一种 回溯算法,用于生成一个集合的所有子集。给定一个数组 arr
,该算法递归地遍历所有可能的子集,并通过一个辅助数组 x
标记当前元素是否被选中。
2.算法特点
-
时间复杂度:O(2n)O(2n)(因为一个包含
n
个元素的集合有 2n2n 个子集)。 -
空间复杂度:O(n)O(n)(递归栈深度)。
-
适用场景:需要枚举所有子集的问题,如组合、子集和、幂集等。
3.代码实现
#include <iostream>
#include <string>using namespace std;
void func(int arr[], int i, int length,int x[])
{
if (i == length)//递归终止条件:处理完所有元素
{
for (int j = 0; j < length; j++)
{
if (x[j] == 1)//如果当前元素被选中,则输出
cout << arr[j] << " ";
}
cout << endl;
}
else
{
x[i] = 1;//选择当前节点
func(arr, i + 1, length,x);//处理左子树
x[i] = 0;//不选择当前节点
func(arr, i + 1, length,x);//处理右子树
}
}
int main()
{
int arr[] = { 1,2,3 };
int length = sizeof(arr) / sizeof(int);
int x[3] = { 0 };//初始化数组为0
func(arr, 0, length,x);
return 0;
}
在此列一道题目:在给出序列中,求所选元素和 与 未选元素和的最小差值是多少
#include <iostream>
#include <cmath>using namespace std;
// 定义全局变量
int arr[] = { 12, 6, 7, 11, 16, 3, 9 }; // 输入的数字数组
const int length = sizeof(arr) / sizeof(int); // 计算数组长度int x[length] = { 0 }; // 记录当前选择的元素(1表示选中,0表示未选中)
int sum = 0; // 记录当前已选元素的和
int r = 0; // 记录当前未选元素的和
int Min = 0x7FFFFFFF; // 记录最小差值,初始设为最大整数值
int bestx[length] = { 0 }; // 记录最佳选择方案// 回溯函数
void func(int i) {
// 递归终止条件:已处理完所有元素
if (i == length) {
// 计算当前选择与未选择子集和的绝对差值
int result = abs(sum - r);// 如果找到更小的差值,更新最优解
if (result < Min) {
Min = result; // 更新最小差值
// 保存当前最佳选择方案
for (int j = 0; j < length; j++) {
bestx[j] = x[j];
}
}
}
else {
// 选择当前元素arr[i]的情况
r -= arr[i]; // 从未选和中减去当前元素
sum += arr[i]; // 将当前元素加到已选和
x[i] = 1; // 标记当前元素为已选
func(i + 1); // 递归处理下一个元素// 不选择当前元素arr[i]的情况
sum -= arr[i]; // 从已选和中减去当前元素
r += arr[i]; // 将当前元素加到未选和
x[i] = 0; // 标记当前元素为未选
func(i + 1); // 递归处理下一个元素
}
}int main() {
// 计算数组所有元素的总和,初始化未选和r
for (int v : arr) {
r += v;
}// 从第0个元素开始回溯搜索
func(0);// 输出结果
cout << "Selected: ";
// 输出被选中的元素
for (int i = 0; i < length; i++) {
if (bestx[i]) {
cout << arr[i] << " ";
}
}
// 输出最小差值
cout << "\nMin difference: " << Min << endl;return 0;
}
继续列一道题:给出2n个整数,从里面挑选n个整数,使其让选择的整数的和 与未选择的整数的和的差值最小
#include <iostream>
#include <cmath>
#include <vector>// 定义全局变量
int arr[] = {12,6,7,11,16,3,8,9}; // 输入的数字数组
const int length = sizeof(arr)/sizeof(int); // 计算数组长度
std::vector<int> x; // 当前选择的元素集合(存储元素值)
std::vector<int> bestx; // 最佳选择的元素集合
int sum; // 当前已选元素的和
int Left = length; // 剩余未处理的元素个数(初始为总长度)
int r; // 当前未选元素的和
unsigned int Min = INT_MAX; // 记录最小差值(初始为最大整数值)
int cnt; // 记录递归调用次数(调试用)// 回溯函数(i表示当前处理元素的索引)
void func(int i) {
// 递归终止条件:已处理完所有元素
if (i == length) {
cnt++; // 递归次数统计
// 检查是否恰好选中一半元素
if (x.size() != length/2) return;
// 计算当前差值
int result = abs(sum - r);
// 更新最优解
if (result < Min) {
Min = result; // 更新最小差值
bestx = x; // 深拷贝当前选择路径
}
return;
}
// 未处理完所有元素时的递归操作
else {
Left--; // 减少剩余未处理元素数量
// 分支1:选择当前元素(需满足选择数量未过半)
if (x.size() < length/2) { // 剪枝条件1:已选数量不能超过半数
// 选择当前元素
sum += arr[i]; // 更新已选和
r -= arr[i]; // 更新未选和
x.push_back(arr[i]); // 记录选择路径
func(i+1); // 递归处理下一个元素
// 回溯操作
sum -= arr[i]; // 恢复已选和
r += arr[i]; // 恢复未选和
x.pop_back(); // 移除当前选择
}
// 分支2:不选择当前元素(需满足剩余元素足够凑够半数)
if (x.size() + Left >= length/2) { // 剪枝条件2:剩余元素足够完成选择
func(i+1); // 递归处理下一个元素
}
Left++; // 恢复剩余未处理元素数量
}
}int main() {
// 初始化未选和(总和)
for(int num : arr) {
r += num;
}
func(0); // 从第0个元素开始回溯
// 输出结果
std::cout << "Selected elements: ";
for(int v : bestx) {
std::cout << v << " ";
}
std::cout << "\nMinimum difference: " << Min << std::endl;
std::cout << "Total recursions: " << cnt << std::endl;
return 0;
}