枚举所有子集的三种算法详解-《算法入门经典》
方法一:增量构造法
理解递归必须得理解函数到底是做什么的。
#include<cstdio>
void print_subset(int n,int *a,int cur) //print_subset的功能是打印集合{0,1,...,n-1}的cur个元素的子集,并且目前a数组中已经存储了要打印的集合
{
printf("{ ");
for (int i=0;i<cur;i++) printf("%d ",a[i]);
printf("}\n");
int mi=cur?a[cur-1]+1:0; //利用字典序避免重复打印
for (int i=mi;i<n;i++) //枚举下一位可以添加什么元素
{
a[cur]=i;
print_subset(n,a,cur+1); //递归打印cur+1个元素的子集
}
}
int main()
{
int n;
int a[10]={};
scanf("%d",&n);
print_subset(n,a,0); //初始时a中存储了0个元素的子集,开始递归打印
return 0;
}
方法二:位向量法
枚举每一位选或者不选,复杂度比方法一略高但更好理解,因为与输出全排列思路差不多,满n位就输出。
#include<cstdio>
int a[10];
void print_subset(int n,int *b,int cur) //确定第cur位选或者不选
{
if (cur==n) //如果确定好了n位,打印结果
{
printf("{ ");
for (int i=0;i<n;i++)
if (b[i]) printf("%d ",a[i]);
printf("}\n");
}
else
{
b[cur]=1; //这一位选
print_subset(n,b,cur+1); //递归下一位
b[cur]=0; //这一位不选
print_subset(n,b,cur+1); //递归下一位
}
}
int main()
{
int n;
int b[10]={};
scanf("%d",&n);
for (int i=0;i<n;i++) a[i]=i;
print_subset(n,b,0);
return 0;
}
缺点是输出不是按照字典序。
方法三:二进制法
稍加思考就会发现,方法二其实与二进制是对应的。
#include<cstdio>
int a[10];
void print_subset(int n,int b) //n位的集合,b状态打印
{
printf("{ ");
for (int i=0;i<n;i++)
if (b&(1<<i)) printf("%d ",a[i]);
printf("}\n");
}
void p_s(int n)
{
for (int i=0;i<(1<<n);i++) print_subset(n,i); //枚举所有状态
}
int main()
{
int n;
scanf("%d",&n);
for (int i=0;i<n;i++) a[i]=i;
p_s(n);
return 0;
}
这个方法优点就是代码简单。
注意:以上三种方法输出顺序不同。