概述
问题描述
已知(w1, w2, …, wn)和M,均为正数。要求找出wi的和数等于M的所有子集。
例如:若n=4,(w1,w2,w3,w4)=(11,13,24,7),M=31,则满足要求的子集是(11,13,7)和(24,7).
分析
子集和数问题解的一种表示方法
- 解由n-元组(x1, x2, …, xn)表示;
- 显式约束条件xi∈{0,1},1≤i≤n,如果没有选择Wi,则xi=0;如果选择了Wi,则xi=1。于是上面的解可以表示为(1,1,0,1)和(0,0,1,1);
- 隐式约束条件(xi× wi)的和数为M
- 解空间的大小为2n个元组
#include<stdio.h>
// 宏定义最大个数MAX
#define MAX 10000
// 设置集合个数
int data[MAX] ;
// 是否选择该元素(True或False)对应与data集合的映射v
bool v[MAX] ;
// 集合元素的个数n,用户输入的子集和目标值c
int n , c ;
// 回溯函数traceback
bool traceback(int n)
{
// p可以看成是一个指针pointer,或者更准确来说应该是一个游标
// 作为我们[1]数组data和[2]data数组映射子集选中数组v的[游标]
// sum是当前正在跑着的子集的和的值
// 对p和sum进行初始化
// 注:p是游标,在C语言中,数组元素下标默认从0开始
int p = 0 ,sum = 0 ;
// 判断当前游标的位置
// 游标位置p<0则退出循环
while(p>=0)
{
/* 选择新进来的元素 */
// 当v[p]==0进入循环
if(!v[p])
{
// 子集中选中v[p]
v[p] = true ;
// 计算子集和,在sum上进行累加data[p]
sum += data[p] ;
// 判断当前累加的子集和sum和目标子集和c
// 如果相等
if(c == sum)
// 返回true
// 找到子集和的解
return true ;
// 如果当前累加的子集和sum > 目标值c
else if( c < sum)
{
// 不选择当前的a[p]值(重置v[p]为False)
// 并在当前子集和sum的基础上减去加上的a[p]
v[p] = false ;
sum -=data[p] ;
}
// 移动游标到下一位
p++ ;
}
/* 回溯过程(到最后一个元素都未找到解进入) */
// 检查游标位置p是否跑完数组data集合所有n个元素
// 如果当前游标值p大于等于集合数组data的元素个数n则进入回溯过程
if(p>=n)
{
// 到最后一个元素还没加到c,sum<c
// 最后一个元素为True
// 回溯到上次为False的地方在重新开始
while( v[p-1] )
{
// p游标自减
p-- ;
// 对当前v[p]赋值
v[p] = false ;
// 跑完整个data数组的n个元素
if(p<1) return false ;
}
//0 1 0 1 0 0 0 1
// 到最后一个元素sum>c
// 最后一个元素为False
// 回溯到上次为Ture的地方在重新开始
while( !v[p-1])
{
p-- ;
if(p<1) return false ;
}
// 回溯过程当前子集和sum也回溯到上次
sum -= data[p-1] ;
// 同时对当前回溯的v[p]重置为false(相当于不要这个元素)
v[p-1] = false ;
}
}
// 找不到满足子集和目标值的子集和解
// 退出回溯函数
return false ;
}
// 主函数
int main()
{
// 读入集合元素个数n,子集和目标值c
scanf("%d %d" , &n , &c) ;
// 依次读入集合中的n个元素
for(int i = 0 ; i < n ; i++)
scanf("%d" , &data[i]) ;
// 用回溯法计算子集和
// 判断是否存在结果(存在即traceback的值为True)
// 输出结果
if(traceback(n))
{
// traceback函数返回值为true
// 则得到目标值子集和的解
// 输出函数结果
int first = 1 ;
// i遍历v,范围是[0, n),即整个集合data数组以及数组v
for(int i = 0 ; i < n; i++)
// 判断v中第(i+1)个元素(v[i])的值是否为0(是否被选中为子集中的元素)
if(v[i])
{
// v[i]被选中了作为子集和中的元素
// first的值为非0
if(first)
// first变量重置为0
first = 1;
// first的值为0
else
printf(" ") ;
printf("%d " , data[i]) ;
}
printf("n") ;
}
// traceback函数返回值为False
// 无法得到该目标值子集和的解
else
printf("No Solution!n") ;
return 0 ;
}
引申
一、子集树
**子集树:**当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间称为子集树。
例如,那个物品的0-1背包问题所相应的解空间树就是一颗子集树。这类子集问题通常有2^n
个叶节点,其节点总个数为2(n+1)-1。遍历子集树的任何算法均需要O(2n)的计算时间。
void backtrack (int t)
{
if (t>n) output(x);
else
for (int i=0;i<=1;i++) {
x[t]=i;
if (legal(t)) backtrack(t+1);
}
}
二、排列树
排列树:当所给问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。
排列树通常有n!个叶子节点。因此遍历排列树需要O(n!)的计算时间。
void backtrack (int t)
{
if (t>n) output(x);
else
for (int i=t;i<=n;i++) {
swap(x[t], x[i]);
if (legal(t)) backtrack(t+1);
swap(x[t], x[i]);
}
}
最后
以上就是害怕便当为你收集整理的子集和数问题——回溯法(C++)的全部内容,希望文章能够帮你解决子集和数问题——回溯法(C++)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复