在紫书上看到的,挺有意思。
一看到位运算我就会躲,因为我整不明白。
代码
#include "iostream"
#include "cstdio"
using namespace std;
void subset(int n, int s) {
printf("{");
for (int i = 0; i < n; i++)
if (s & (1 << i)) printf("%d ", i+1);
printf("}\n");
}
int main() {
int n;
while (scanf("%d", &n) != EOF)
for (int i = 0; i < (1 << n); i++)
subset(n, i);
return 0;
}
这段代码就是输入n,输出1~n的所有子集。
位运算是玄学,但是时空效率和代码长度都比之前使用其他的递归方法来计算好一些。当然当n过大还是会不行。
思路
可以使用一串二进制数来表示一个1~n的子集,从右往左分别是\(2^0,2^1,2^2,...,2^n-1\)。
下图代表全集\(Q=\{x|1\le x\le 16\}\)中的一个子集,书上的最右侧是0,但我们选择从1开始。
当第i位为0时,我们就认为子集中不包含这个元素,当第i位为1时,认为包含这个元素,所以上图是代表集合\(S=\{1,2,3,5,6,10,11,15\}\)。这里注意因为我们的最右侧是从1开始的,而上图是从0开始的。
那么这个n位的二进制数足以通过其中1/0的状态不同来表示1~n的所有子集。这个n位的二进制数的十进制范围就是\(0-2^{n-1}\)。
下面我们来介绍两个基本操作,与和左移位。会的直接跳过。
位与操作自然不用我说,对应位做与运算,与操作对应的集合操作是求交集,如{1,0,1} & {0,1,1} = {0,0,1}
。转换成上面的集合表示就是{3,1} & {2,1} = {1}
。
左移一位相当于乘2操作,\(0001 << 1 = 0010\),\(0010 << 1 = 0100\)。转换成十进制就是\(1*2=2和2*2=4\)。
下面我们来看代码。
首先是主函数中的这一段:
for (int i = 0; i < (1 << n); i++)
subset(n, i);
循环遍历0~2^n-1中的每一个数,这些数就构成了1~n的所有子集。相当于对每一个子集调用subset来输出。而且i单调递增,这也能保证输出的东西是字典序的。
然后是subset
for (int i = 0; i < n; i++)
if (s & (1 << i)) printf("%d ", i+1);
这个循环相当于打印出第i位的值为1的数。(1<<i)
会产生诸如00001,00010,00100,01000,10000
这样的数去检查s
的每一位,然后和s做交集,这样产生的结果就是当第i位为1时条件就会为真,当第i位为0时(也就是不在子集中)条件就会为0(因为&操作得到了一个空集)。
标签:subset,运算,int,所有,二进制,子集,位为 来源: https://www.cnblogs.com/lilpig/p/14036889.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。