幂集的位运算方法和递归算法

#include<bits/stdc++.h>
using namespace std;
vector<int> cur;
vector<vector<int>> ans;
vector<vector<int>> fun(vector<int> &nums)
{
	int n=nums.size();
	for(int i=0;i<(1<<n);i++)   // 遍历所有可能的子集
	{
		cur.clear();
		for(int j=0;j<n;j++)   // 对于每一位,判断是否包含在当前子集中
		{
			if(i&(1<<j))
			{
				cur.push_back(nums[j]);
			}
		}
		ans.push_back(cur);    // 将当前子集添加到答案中
	}
	return ans;
}
int main()
{
	vector<int> nums;
	int n;
	cin>>n;
	while(n--)
	{
		int q;
		cin>>q;
		nums.push_back(q);
		if(cin.get()=='\n') break;
	}
	vector<vector<int>> ans1;
	ans1=fun(nums);
	for(auto e:ans1)
	{
		for(auto w:e)
		{
			cout<<w;
		}
		cout<<endl;
	}
	return 0;
}

给出数组长度:4

数组为:nums=[1,2,3,4]

相应的输出为:

上面给出的是位运算得出幂集的方法,详细逻辑思路以及位运算的一些用法如下:


所以,最终生成的子集(按照i的遍历顺序)是:

[ [], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]

  • 左移(<<)(不好记的话,可以看尖角指向的是左就是左移)

    左移操作符(<<)用于将一个数的所有位向左移动指定的位数。例如,假设我们有一个二进制数0010(十进制中的2),对它左移1位得到0100(十进制中的4)。每向左移动一位,基本上相当于乘以2。因此,1 << n实际上是生成了值2^n

  • 左移操作(<<)相当于乘以2的幂次方。

  • 右移(>>)(这里右移则尖角指向右方)

    右移操作符(>>)与左移相反,它将一个数的所有位向右移动指定的位数。右移通常被用于除以2的幂次方。例如,8 >> 1将得到4,因为8的二进制形式1000向右移一位变成了0100(即十进制中的4)。

  • 右移操作(>>)相当于除以2的幂次方(对于无符号整数)。

  • 代码通过位操作和掩码技术高效地遍历了所有子集的可能组合。
  • 代码中使用了左移来生成所有可能的子集。核心思想是利用一个整数的每一位代表原集合中的一个元素,该位是否为1表示该元素是否被选中。
  • 可能说的笼统了些,下面我们就用这个代码来看看过程

  • 以输入nums = [1, 2, 3]为例,解释该代码如何生成所有子集。


    首先,nums数组的长度n为3,所以1 << n等于1 << 3等于8

  • (  i为外层循环 ———>for(int i=0;i<(1<<n);i++)中的   i  。这个循环从0迭代到2^n - 1(因为1 << n生成了2^n,我们需要的是从0到2^n - 1的所有数)每一个i代表了一种子集的可能性。 )

  • 这意味着会有2^n = 2^3 = 8个子集,包括空集和自身。遍历的i值从07(即二进制的000111),每一个i都代表了一个可能的子集。

    接下来,我们逐个分析每个i值如何产生对应的子集:

  • i = 0 (二进制 000): 没有位被设置,因此没有元素被选中。子集是[]
  • i = 1 (二进制 001): 只有最低位被设置,表示只选取nums中的第三个元素(索引从0开始)。子集是[3]
  • i= 2 (二进制 010): 第二位被设置,表示选取nums中的第二个元素。子集是[2]
  • i = 3 (二进制 011): 第一和第二位被设置,表示选取nums中的第二个和第三个元素。子集是[2, 3]
  • i = 4 (二进制 100): 第三位被设置,表示选取nums中的第一个元素。子集是[1]
  • i = 5 (二进制 101): 第一和第三位被设置,表示选取nums中的第一个和第三个元素。子集是[1, 3]
  • i = 6 (二进制 110): 第二和第三位被设置,表示选取nums中的第一个和第二个元素。子集是[1, 2]
  • i = 7 (二进制 111): 所有位都被设置,表示选取nums中的所有元素。子集是[1, 2, 3]

再次对核心代码解释

for(int j=0;j<n;j++)
		{
			if(i&(1<<j))
			{
				cur.push_back(nums[j]);
			}
		}

这里,i变量是一个整数,它通过位运算来表示当前遍历的子集。在二进制表示中,i的每个位对应nums数组中的一个元素。如果i的某一位是1,则表示相应位置的元素应该被选中加入到当前的子集cur中;如果是0,则表示相应的元素不被选中。

  • for (int j = 0; j < n; j++): 这个循环遍历nums数组的每个元素,nnums数组的长度。

  • 1 << j: 这个表达式将数字1向左移j位。移位操作实际上是在创建一个只在第j位为1的二进制数,用于检查i的第j位是否被设置(即是否为1)。例如,如果j为2,则1 << 2的结果是100(二进制),即十进制的4

  • i & (1 << j): 这个表达式执行按位与操作。它检查i的第j位是否为1。如果i的第j位为1,那么这个表达式的结果为非零值(真),表示nums数组中对应的元素应该被选中。如果i的第j位为0,结果为0(假),表示不选择该元素。

  • cur.push_back(nums[j]): 如果上述条件满足(即i的第j位为1),则将nums数组中的第j个元素添加到当前子集t中。

通过这个循环,我们能够根据i的每个位是否为1来决定是否选择nums数组中对应位置的元素。这样,通过从02^n - 1变化i值的过程,我们能遍历并生成nums数组的所有可能子集。



下面再提供相应的递归算法,如果你有兴趣,可以根据下面算法进行代码实现。

vector<vector<int>> sub(vector<int>& nums,int n)
{
    if(n==0) return {{}};
    auto temp=sub(nums,n-1);
    auto ans=temp;
    for(auto num:temp)
    {
        num.push_back(nums[n-1]);
        ans.push_back(num);
    }
    return ans;
}
    vector<vector<int>> subsets(vector<int>& nums) {
        int n=nums.size();
        return sub(nums,n);
    }
}

快去写一次吧,相信大侠会获得新知识。

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值