使用位运算得到所有子集

在紫书上看到的,挺有意思。

一看到位运算我就会躲,因为我整不明白。

代码#

#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的子集,从右往左分别是20,21,22,...,2n1

下图代表全集Q={x|1x16}中的一个子集,书上的最右侧是0,但我们选择从1开始。

当第i位为0时,我们就认为子集中不包含这个元素,当第i位为1时,认为包含这个元素,所以上图是代表集合S={1,2,3,5,6,10,11,15}。这里注意因为我们的最右侧是从1开始的,而上图是从0开始的。

那么这个n位的二进制数足以通过其中1/0的状态不同来表示1~n的所有子集。这个n位的二进制数的十进制范围就是02n1

下面我们来介绍两个基本操作,与和左移位。会的直接跳过。

位与操作自然不用我说,对应位做与运算,与操作对应的集合操作是求交集,如{1,0,1} & {0,1,1} = {0,0,1}。转换成上面的集合表示就是{3,1} & {2,1} = {1}

左移一位相当于乘2操作,0001<<1=00100010<<1=0100。转换成十进制就是12=222=4

下面我们来看代码。

首先是主函数中的这一段:

for (int i = 0; i < (1 << n); i++)
    subset(n, i);

循环遍历02n1中的每一个数,这些数就构成了1n的所有子集。相当于对每一个子集调用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(因为&操作得到了一个空集)。

posted @   yudoge  阅读(255)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示
主题色彩