位运算(2)-- 集合中的位运算

来源:互联网 发布:淘宝客服要开通管理 编辑:程序博客网 时间:2024/06/16 11:15

一、基本概念

当集合元素个数较少时,可以用二进制来表示。集合{0, 1, ..., n - 1}的子集S可以用如下方法编码成整数:

f(S)=iS2i

本质就是用每一个二进制位来表示某个元素是否出现。

  • 空集 ϕ0
  • 只含有第i个元素的集合{i}1 << i
  • 含有全部n个元素的集合{0, 1, ..., n - 1}(1 << n) - 1
  • 判断第i个元素是否属于集合SS >> i & 1,or S & (1 << i)
  • 向集合中加入第i个元素S U {i}S | 1 << i
  • 向集合中去除第i个元素S \ {i}S & ~(1 << i)
  • 集合ST的并集 STS | T
  • 集合ST的交集 STS & T
  • 枚举全集S的所有子集:for(int s = 0; s < (1 << n); s++) {/* 处理子集 */}

二、固定集合的子集遍历

问题:给定一个含有若干元素的集合s(s是一个二进制数),枚举该集合的所有子集,含有n个元素的集合共有 2n 个子集。

分析:找到s中1的个数和位置,然后通过删除若干个1来获得s的所有子集。为了获得所有的子集可能需要对之前删掉的1进行二次访问,可以通过和原始集合s进行与操作实现对之前删掉的1的二次访问。伪代码如下:

int subset = s;do{    // 处理子集    subset = (subset - 1) & s;}while(subset != s);    // 当subset为-1的时候说明所有的子集已经遍历完

三、集合的K元素子集

问题:遍历一个集合的所有k元素子集。例如,含有4个元素的所有子集为:00001111->00010111->00011011->...->11110000
分析:分四步解决:以00010111 --> 00011011为例
1. 求出从最低位的1开始的连续的1的区间 (00010111 --> 00000111)
2. 将此区间全部变为0,并将区间左侧的那个0变为1 (00010111 --> 00011000)
3. 将第1步取出的区间右移,直到剩下的1的个数减少一个 (00000111 --> 00000011)
4. 将第2步和第3步的结果异或 (00011000 | 00000011 = 00011011)
伪代码描述

int comb = (1 << k) - 1;        // 最小的 k 元素子集while(comb < (1 << n)){    // 处理子集    /* 得到下一个子集 */    int x = comb & -comb;    // x 表示最低位1的位置    int y = comb + x;        // y 表示第2步操作的结果    comb = (((comb & ~y) / x) >> 1) | y;}

python代码

#!/usr/bin/python# -*- coding: utf-8 -*-def subset(k, n):     comb = (1 << k) - 1     while comb < (1 << n):        print("next set: ", comb, bin(comb)[2:])        x = comb & -comb        y = comb + x        comb = ((int((comb & ~y) * 1.0 / x)) >> 1) | ysubset(4, 8)

四、子集生成

问题:给定一个集合,枚举所有可能的子集。
实现

// 枚举子集for(int s = 0; s < (1 << n); ++s){    // 输出子集S对应的各个元素    for(int i = 0; i < n; ++i){        if(s & (1 << i)) // or (s >> i) & 1            printf("%d ", i + 1);    }    printf("\n");}
1 0
原创粉丝点击