问题描述
已知(w1, w2, …, wn)和M,均为正数。要求找出wi的和数等于M的全部子集。web
例如:若n=4,(w1,w2,w3,w4)=(11,13,24,7),M=31,则知足要求的子集是(11,13,7)和(24,7).算法
分析数组
子集和数问题解的一种表示方法svg
#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 ; }
1、子集树函数
**子集树:**当所给的问题是从n个元素的集合S中找出知足某种性质的子集时,相应的解空间称为子集树。spa
例如,那个物品的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); } }
2、排列树
排列树:当所给问题是肯定n个元素知足某种性质的排列时,相应的解空间树称为排列树。code
排列树一般有n!个叶子节点。所以遍历排列树须要O(n!)的计算时间。
xml
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]); } }