集合中的常见的位运算

本文探讨了如何使用二进制表示集合及其操作,包括按位运算(&、|、⊕、∼)、子集判断、子集的生成与非空子集的递归遍历,以及如何通过二进制操作扩展最大元素。作者提供了C++代码示例来演示这些概念。
摘要由CSDN通过智能技术生成

集合与二进制的联系

集合可以用二进制表示,二进制从低到高第 i位
i 位为 1,表示 i 在集合中,
i 位为 0,表示 i 不在集合中。

例如集合 {0,2,3}

{0,2,3} 可以用二进制数 1101表示;反过来,二进制数 1101;1101 就对应着集合 {0,2,3}。

对于某个非负整数的集合中,可以用数字表示:

集合与集合

其中 && 表示按位与,∣∣ 表示按位或,⊕⊕ 表示按位异或,∼∼ 表示按位取反。 

其中「对称差」指仅在其中一个集合的元素。 

 

子集的判断 

这时如果我们添加一个元素,并且这个元素包含了 0 , 2 , 3,如29,用二进制表示:1 1101,29包含了集合中的所有元素(包含了元素的所有的1),意味着29是最大的集合

由此我们引出了集合a是集合b的子集用二进制的表示方法:

a&b = a,表示a是b的子集,反之则不是

a | b = b,等价于上

集合与元素

  

S & (S-1) 

如何快速找到下一个子集呢?以 101100→101001 为例说明,

普通的二进制减法会把最低位的 1 变成 0,同时 1 右边的 0 变成 1,即 101100→101011。

「压缩版」的二进制减法也是类似的,把最低位的 1 变成 0,同时对于 1右边的 0 变成 1,只保留在 s=101101 中的 1所以是 101000→10001。怎么保留?& 10101 就行。

               s = 101100
            s-1 = 101011 // 最低位的 1 变成 0,同时 1 右边的 0 都取反,变成 1
   s & (s-1) = 101000
 

这里要注意:

1.第三步计算后的最小元素作为新的s是从右到左找第i位为1的元素

如此原本为{2,3,5}的集合删除了最小的元素后变成了{3,5} 。这意味着如果s的二进制位上只有一个1或者都是0时(2的幂)s&(s-1)后的结果会删除这个1,等于0 

2.如果初始S维护起来,计算后的最小元素用变量来接收,用这个每一次枚举都要更新变量的值&S,则是找出全部的非空子集。

//设集合为 s,从大到小枚举 s 的所有非空子集 sub
for (int sub = s; sub; sub = (sub - 1) & s) {
    // 处理 sub 的逻辑
}

---------------------------------------------------------------------------------------------------------------------------------

介绍完s & (s-1)(集合的缩小),下面就要介绍怎么得到最小的集合,也就是怎么得到以最低位的1为最高位的二进制数,如100 1100中的 100 

S & (-S) 

这里仍用集合{2,3,5}演示过程

                s = 101100  ~s = 010011 

               -s = 010100 //相反数等于补码取反+1
      s & (-s) = 000100

如此我们取到了第一个最小的元素 2 ,接下来要取到第二小的元素呢?

不难猜出->我们要在此之前删除这个用过的最低为的1,那么就要用到异或操作(也可以是减法)

我们用lowbit来记录每一次计算出来的最小元素 

        lowbit = s & (-s) = 000100

        S = S ^ lowbit    = 101100

                                   ^  000100

                                S = 101000

如此反复就能完成每一次的最小元素的取出了。

代码实现: 

下面是对于以上的代码实现:

为了方便展示和观察,示例用的是 21

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <intrin.h>

using namespace std;

class Solution
{
public:
    void NumbersInSet(int num)/*统计集合中的每个元素(从大到小枚举集合的所有非空子集)
                          10101 -> 10100 -> 10001 -> 10000 -> 00101 -> 00100 -> 00001 
    相当于缩减版的二进制减法  1 1 1    1 1 0    1 0 1    1 0 0    0 1 1    0 1 0    0 0 1
    */
    {
        int lowbit = num & (num - 1);
        while (lowbit)
        {
            cout << lowbit << " ";
            lowbit = num & (lowbit - 1);
        }
    }

    void NumbersInSetExtension(int num)/*扩展最大元素,包含着此时的子集的更大集合
                  10101 -> 10111 -> 11101 -> 11111 ->110101 -> 110111 -> 111101 -> 111111    
                  ( 21 )   ( 23 )   ( 29 )   ( 31 )  ( 53 )    ( 55 )    ( 61 )    ( 63 )
                  _0_0_    _0_1_    _1_0_    _1_1_   1_0_0_    1_0_1_    1_1_0_    1_1_1_ 
    相当于缩减版的二进制加法                               
    */                                     
    {
        int n = 6;
        int j = 0;
        int lowbit;
        long long ans = num;
        int x = num;
        x = ~x;//此时的二进制的1就是之前的0,可以填入的位置
        int count = 0;
        while (++count <= n)//从 0 1 开始填入 直到为n
        {
            while (count >> j)
            {
                lowbit = x & (-x);//num集合中最小元素
                ans |= (long long)lowbit * ((count >> j) & 1);
                ++j;
                x ^= lowbit;//差
            }
            cout << ans << " ";
            j = 0;
            x = ~num;
            ans = num;//重新选择新的num填入count的二进制位
        }
    }       
};

int main()
{
    int n;
    cin >> n;
    Solution test;
    test.NumbersInSet(n);
    cout << endl;
    test.NumbersInSetExtension(n);

    return 0;
}

文中的~x是为了方便快速找到可以填入的空位(空位取反后是1) 

 

以上便是对集合的内的元素/非空子集遍历元素的扩展遍历

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值