方法一:
增量构造法,也就是一次选一个元素放到上一次构造的子集中,先给出代码:
void print_subset(int n,int* A,int cur){
for(int i=0;i<cur;i++)
printf("%d ",A[i]);
printf("\n");
int s=cur?A[cur-1]+1:0; //确定当前元素的最小可能值
for(int i=s;i<n;i++){
A[cur]=i;
print_subset(n,A,cur+1);
}
}
从上面的代码中可以看出每次递归调用都要输出当前集合,然后在当前的集合里添加一个元素进行下一次递归,直到无法继续添加元素,自然就不会再递归了,因此程序中也就没有显式的给出递归边界。(上面的代码用到了定序的技巧:规定集合A中所有元素的编号从小到大排列,避免同一个集合枚举两次)
方法二:
位向量法,就是为原集合中的每个元素设置一个位向量Bi,当且仅当B[i]=1的时候标志着i在当前的子集中。
void print_subset(int n,int* B,int cur){
if(cur==n){
for(int i=0;i<n;i++){
if(B[i]) printf("%d ",i);
}
printf("\n");
return;
}
B[cur]=1; //选第cur个元素
print_subset(n,B,cur+1);
B[cur]=0; //不选第cur个元素
print_subset(n,B,cur+1);
}
方法三:
二进制法,这个方法确实巧妙,读者应该好好欣赏一下这个方法。意思就是每一个子集都用一个二进制数来代表,因为二进制数的每一位非0即1,从右往左第i位(各位从0开始编号)表示元素i是否在集合S中。例如二进制数1111代表集合{0,1,2,3},1011则代表它的一个子集{0,1,3}
所以集合{0,1,2,3}的所有子集分别对应着从0000~1111的所有二进制数,对应到10进制数则为0~2^n-1。
下面给出如何通过一个十进制数得到它所对应的二进制数的各个位上的数值(即判断第i位是0还是1,以此来确定元素i是否在集合S中)
void print_subset(int n,int s){
for(int i=0;i<n;i++){
if(s&(1<<i)) printf("%d ",i);
}
printf("\n");
}
核心代码就一句话s&(1<<i)
,例如s=5,对应的二进制是101,n=3,for循环中i分别取0,1,2, 1<<i
分别取001,010,100,它们分别和101进行与运算后得到了001,000,100,因为运算结果中只有000为0,所以只有元素2不在这个子集当中,所以得到的子集是{0,1}。
接下来贴总的代码:
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <ctype.h>
#include <iostream>
#include <sstream>
#include <map>
#include <list>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define eps 1e-8
#define INF 0x7fffffff
#define PI acos(-1.0)
#define seed 31//131,1313
#define MAXV 50010
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
void print_subset(int n,int s){
for(int i=0;i<n;i++){
if(s&(1<<i)) printf("%d ",i);
}
printf("\n");
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<(1<<n);i++){ //枚举各子集所对应的编码0, 1, 2,..., 2^n-1
print_subset(n,i);
}
}
}