使用位运算得到所有子集
在紫书上看到的,挺有意思。
一看到位运算我就会躲,因为我整不明白。
代码#
#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的子集,从右往左分别是。
下图代表全集中的一个子集,书上的最右侧是0,但我们选择从1开始。
当第i位为0时,我们就认为子集中不包含这个元素,当第i位为1时,认为包含这个元素,所以上图是代表集合。这里注意因为我们的最右侧是从1开始的,而上图是从0开始的。
那么这个n位的二进制数足以通过其中1/0的状态不同来表示1~n的所有子集。这个n位的二进制数的十进制范围就是。
下面我们来介绍两个基本操作,与和左移位。会的直接跳过。
位与操作自然不用我说,对应位做与运算,与操作对应的集合操作是求交集,如{1,0,1} & {0,1,1} = {0,0,1}
。转换成上面的集合表示就是{3,1} & {2,1} = {1}
。
左移一位相当于乘2操作,,。转换成十进制就是。
下面我们来看代码。
首先是主函数中的这一段:
for (int i = 0; i < (1 << n); i++)
subset(n, i);
循环遍历中的每一个数,这些数就构成了的所有子集。相当于对每一个子集调用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(因为&操作得到了一个空集)。
作者:Yudoge
出处:https://www.cnblogs.com/lilpig/p/14036889.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
欢迎按协议规定转载,方便的话,发个站内信给我嗷~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)