集合的表示及其运算

说明:当状态的维数比较多,但每个维数都只有两种情况时,可以考虑用集合来表示。下面来说明集合的表示及其运算。

一,集合的表示及基本操作

C语言中集合采用二进制码来表示,相当于将集合编码成一个整数。利用公式f(S)=∑2^i(i∈S)即可将集合编码成为一个整数。下面假设集合中一共有n个元素,编号从0开始。

  1. 空集:0
  2. 全集:(1<<n)-1
  3. 只含有第i个元素的集合{i}:1<<i
  4. 判断第i个元素是否属于集合{0,1……n-1}:if((S>>i)&1)
  5. 向集合中加入第i个元素S∪{i}:S|=1<<i
  6. 从集合中除去第i个元素S\{i}:S&~(1<<i)
  7. 集合S和T的并集S∪T:S|T
  8. 集合S和T的交集S∩T:S&T
  9. 集合S为全集,s为其一个子集,s的补集:s^S

二,枚举子集

枚举子集可以采用从大到小逆序枚举(因为顺序枚举有很多缺陷)。假设全集为S,令s为S的子集,那么最初令s=S,之后每次减一,由于减掉之后可能不是S的子集,因此应该再取交集:(s-1)&S。由此不难写出枚举的代码:

for (int s = S; s >= 0; s = (s - 1)&S)
	{
		//对子集的处理
	}

三,枚举集合{0,1……n-1}所包含的所有大小为k的子集

按照字典序的话,最小的子集是(1<<k)-1,因此用它作为初始值,每次都求出子集s的下一个子集。方法如下:

(1)求出最低位的1开始的连续的1的区间,例如(0101110->0001110)

(2)将这一区间全部变为0,并将区间左侧的那个0变为1,例如(0101110->0110000)

(3)将第一步里取出的区间右移,直到剩下的1的个数减少了1个,例如(0001110->0000011)

(4)将第二步和第三步的结果按位取或(取并集),例如(0110000|0000011=0110011)

根据上述的步骤,不难写出如下的代码,注意每一步的写法。

int k,n;
	int s = (1 << k) - 1;
	while (s < 1 << n)
	{
		int x = s&-s;//s&-s的值就是将最低位的1独立出来后的值,设为x
		int y = s + x;//根据第二步的描述,令s+x即可实现,设结果为y
		s = ((s&~y) / x >> 1) | y;//s&~y恰好只剩下最低位的1开始的连续区间,其他地方都为0,假设这个结果是z,那么(z/x)>>1就是第三步的结果,再与y取或,即完成了第四步
	}


 





你可能感兴趣的:(集合)