首先我们来看一个问题,给定一个长度为n的集合a,现在要你求a的所有子集(不包括空集)并打印出来。
这个问题很明显可以用搜索去做,确定一下枚举起点,子集长度不断向后搜索,但是使用搜索很麻烦,所以我们可以用二进制枚举。
首先一个二进制数中只包含0,1,在二进制枚举中0代表不选当前元素,1代表选当前元素。
例如对于一个长度为5的集合{1,2,3,4,5},我们选择1,3,5这3个元素,如下图
a | 1 | 2 | 3 | 4 | 5 |
二进制 | 1 | 0 | 1 | 0 | 1 |
是否选 | 选 | 不选 | 选 | 不选 | 选 |
那么当前选择的状态就可以用二进制10101来表示,也就是十进制中的21。由于对于一个长度为n的集合,它的子集个数就为2^n,我们用十进制来代表当前选择的状态,我们就可以从0到2^n - 1,依次枚举2^n个子集,2^n可以表示为(1<<n),所以代码如下:
for (int i = 0;i < (1<<n); i++) {
......
}
我们知道了枚举了每个子集的状态之后,那怎么把子集元素取出来呢?
例如上面我们在{1,2,3,4,5}中取了1,3,4,二进制为10101,我们枚举的是二进制的十进制形式,也就是21,我们可以把21转化为二进制后取出为1的元素,但这很麻烦。
一个很巧妙的方式就是利用位运算。
1<<0=1(0);
1<<1=2(10);
1<<2=4(100);
1<<3=8(1000);
1<<4=16(10000);
...
1<<7=128(10000000);
...
很容易看出,我们只需要(1<<j) & 21,就可以判断二进制下21的每一位是0还是1,为1时输出a[j](对<<和&不懂的可以提前去看我之前总结的位运算知识点这里),所以可以这样写:
for (int j = 0;j < n; j++) {
if (i & (1 << j)) cout << a[j] << " ";
}
那么这道题的完整代码就可以这样写了
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a[n + 1];
for (int i = 0;i < n; i++) cin >> a[i];
for (int i = 0;i < (1<<n) ;i++) { //从0~2^n-1个状态
for (int j = 0;j < n; j++) { //遍历二进制的每一位
if (i & (1<<j)) cout << a[j] << " "; //如果选了就输出a[j]
}
cout << endl;
}
return 0;
}