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

一、基本概念

当集合元素个数较少时,可以用二进制来表示。集合{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的并集 ST S | T
  • 集合ST的交集 ST S & 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) | y

subset(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");
}

你可能感兴趣的:(数据结构与算法)