集合的整数表示与子集枚举

集合{0,1,…,n-1}的子集S可以编码成整数:f(S)=∑2i,像这样表示之后,一些集合运算可以对应地写成如下方式:

①空集Ø:……………………………………………0

②只含有1个元素i的集合{i}…………………………1<<i

③含有全部n个元素的集合…………………………(1<<n)-1

④判断第i+1个元素i是否属于集合S………………if (S>>i&1)

⑤向集合中加入第i+1个元素i(S∪{i})…………S|1<<i

⑥从集合中取出第i+1个元素(S\{i})……………S&~(1<<i)

⑦集合S和集合T的并集S∪T………………………S|T

⑧集合S和集合T的交集S∩T………………………S&T
此外,想要将集合{0,1,…,n-1}的所有自己枚举出来的话,可以这样写:

for (int S=0; S<1<<n; S++)
{
    //对子集的处理
}

接下来,介绍如何枚举某个集合sup的子集。这里sup是一个二进制码。

(sub-1) & sup

(sub-1)&sup会忽略sup中的0而从sub中减去"1",最终sub可以将sup所有的子集按照降序生成出来。为什么不能用加?因为(sub+1)&sup虽然是sup的子集,但很可能依旧是sub,没有任何改变,而用减就不会出现这样的情况。

int sub=sup;
do
{
  //对子集进行处理  
  sub=(sub-1)&sup;
} while (sub != sup);//处理完0之后,会有-1&sup=sup

最后,介绍如何枚举{0,1,…,n-1}所包含的所有大小为k的子集。通过位运算,按照字典序升序地枚举出所有满足条件地二进制码,先上代码:

复制代码
int comb = (1<<k)-1;
while (comb< 1<<n)
{
    //这里进行针对组合的处理
    int x=comb&(-comb), y=comb+x;
    comb=((comb&~y)/x>>1)|y;
}
复制代码

按照字典序的话,最小的子集是(1<<k)-1,所有用它作为初始值,下面就要求出comb之后一个二进制码了,方法如下:
①求出最低位的1开始的连续的1的区间(0101110→0001110)
②将这一区间全部变为0,并将区间左侧的那个0变为1(0101110→0110000)
③将第①步里取出的区间右移,直到剩下的1的个数减少了1个(0001110→0000011)
④将第②步和第③步的结果按位取或(0110000|0000011→0110011)
对于非零的整数,x&(-x)的值就是将其最低位的1独立出来的值,即lowbit

除上述例子外,还可以利用位运算完成满足其它条件的集合的枚举,例如不包含相邻元素的集合等

猜你喜欢

转载自www.cnblogs.com/Ymir-TaoMee/p/9509521.html