问题描述
之前我们写过背包问题,知道背包问题是找到最有价值的子集,但是如果需要把所有的子集全部输出出来该怎么做呢?
即:给定一个集合,枚举所有的可能的子集。
位向量法
- 思路:
其实我觉得这更可以叫做直接选择法,在需要枚举的1-n中选择第i个选择是否要放入当前子集。 - 代码演示:
#include <iostream>
using namespace std;
const int MAXN = 10000;
int n;//集合中的元素个数
int visit[MAXN];
int subset(int num)
{
if (num == n + 1)//已经判断了n个元素是否选择后输出选择的元素
{
for (int i = 1; i <= num; i++)
{
if (visit[i])
cout << i << " ";
}
cout << endl;
return 0;
}
//选择第num个元素作为子集的一部分
visit[num] = 1;
subset(num + 1);
//不选择第num个元素作为子集的一部分
visit[num] = 0;
subset(num + 1);
return 0;
}
int main()
{
memset(visit, 0, sizeof(visit));
cout << "请输入集合中元素的个数:";
cin >> n;
cout << 0 << endl;//输出0即空集
subset(1);
return 0;
}
运行结果如下:
增量构造法
- 思路:
根据递增顺序构造子集,规定集合A中所有元素的编号从小到大排列, 就不会把集合{1, 2}按照{1, 2}和{2, 1}输出两次了。 - 代码演示:
#include <iostream>
using namespace std;
const int MAXN = 10000;
int n;//集合中的元素个数
int arr[MAXN];//存储子集
int subset(int arr[], int num, int n)
{
if (arr[0] == 0 && num != 1)//去掉arr[0]==0并且num!=1时的子集
{ //因为在有元素的情况下我们不区分是否有空集,即任何非空集合都有空集
}
else
{
for (int i = 0; i < num; i++)//输出子集
{
cout << arr[i] << " ";
}
cout << endl;
}
//确定可以加入子集的元素中的最小值m,当子集个数num=0时最小值为0,当子集个数不为0时最小值等于arr[num-1]+1
//即当前子集最后一个元素加1
int m = num ? arr[num - 1] + 1 : 0;
for (int i = m; i <= n; i++)//选择加入子集中的元素
{
arr[num] = i;//将i加入当前子集中
subset(arr, num + 1, n);//递归构造新的子集
}
return 0;
}
int main()
{
cout << "请输入集合中元素的个数:";
cin >> n;
subset(arr, 0, n);
return 0;
}
运行结果如下:
二进制法
- 思路:
仔细了解生成子集的问题,我们发现其实只需要选择每一位是否选择(即是否添加进当前子集),选择与否,我们可以联想到0和1,由此可以联想到二进制,因为二进制每一位都是0或者1构成。那么我们只需要写出0-111…1(n个1)所有的情况,就是子集。从右往左数第i位为1即代表当前子集中右i。
那么由此我们便可以直接遍历1-(1<<n)输出所有情况即可。 - 代码演示:
#include <iostream>
using namespace std;
int n;
int subset(int m)
{
for (int i = 0; i < n; i++)
{
if (m & 1<<i)//判断从右到左数第i位二进制是否为1
{
cout << i+1 << " ";
}
}
cout << endl;
return 0;
}
int main()
{
cout << "请输入集合中元素的个数:";
cin >> n;
cout << 0 << endl;//输出0,表示空集
for (int i = 1; i < (1 << n); i++)//1<<n表示n+1位2进制数
{
subset(i);
}
}
运行结果如下: