问答题 4.  有一个集合,求其全部子集(包含集合自身)。给定一个集合s,它包含两个元素<a,b>,则其全部的子集为<a,ab,b>。
【正确答案】根据数学性质分析,不难得知,子集个数Sn与原集合元素个数n之间的关系满足如下等式:Sn=2^n-1。
   方法一:位图法
   具体步骤如下所示。
   (1)构造一个和集合一样大小的数组A,分别与集合中的某个元素对应,数组A中的元素只有两种状态:“1”和“0”,分别代表每次子集输出中集合中对应元素是否要输出,这样数组A可以看作是原集合的一个标记位图。
   (2)数组A模拟整数“加1”的操作,每执行“加1”操作之后,就将原集合中所有与数组A中值为“1”的相对应的元素输出。
   设原集合为<a,b,c,d>,数组A的某次“加1”后的状态为[1,0,1,1],则本次输出的子集为<a,c,d>。使用非递归的思想,如果有一个数组,大小为n,那么就使用n位的二进制,如果对应的位为1,那么就输出这个位,如果对应的位为0,那么就不输出这个位。
   例如集合{a,b,c}的所有子集可表示如下:
集合 二进制表示
{}(空集) 0 0 0
{a} 0 0 1
{b} 0 1 0
{c} 1 0 0
{a,b} 0 1 1
{a,c} 1 0 1
{b,c} 1 1 0
{a,b,c} 1 1 1
   算法的重点是模拟数组加1的操作。数组可以一直加1,直到数组内所有元素都是1。实现代码如下:
   def getAllSubset(array,mask,c):
   length=len(array)
   if length==c:
   print "{",
   i=0
   while i<length:
   if mask[i]==1:
   print array[i],
   i+=1
   print "}"
   else:
   mask[c]=1
   getAllSubset(array, mask, c+1)
   mask[c]=0
   getAllSubset(array, mask, c+1)
   
   if __name__="__main__":
   array=['a,'b','c']
   mask=[0,0.0]
   getAllSubset(array, mask, 0)
   程序的运行结果为:
   {abc}
   {ab}
   {ac}
   {a}
   {bc}
   {b}
   {c}
   该方法的缺点在于如果数组中有重复数时,这种方法将会得到重复的子集。
   算法性能分析:
   这种方法的时间复杂度为O(N*2N),空间复杂度O(N)。
   方法二、迭代法
   采用迭代算法的具体过程如下:
   假设原始集合s=<a,b,c,d>,子集结果为r:
   第一次迭代:
   r=<a>
   第二次迭代:
   r=<a ab b>
   第三次迭代:
   r=<a ab b ac abc bc c>
   第四次迭代:
   r=<a ab b ac abc bc c ad abd bd acd abcd bcd cd d>
   每次迭代,都是上一次迭代的结果+上次迭代结果中每个元素都加上当前迭代的元素+当前迭代的元素。
   实现代码如下:
   def getAllSubset(str):
   if str==None or len(str)<1:
   print "参数不合理"
   return None
   arr=[]
   arr.append(str[0:1])
   i=1
   while i<len(str):
   lens=len(arr)
   j=0
   while j<lens:
   arr.append(arr[j]+str[i])
   j+=1
   arr.append(str[i:i+1])
   i+=1
   return arr
   
   if __name__=="__main__":
   result=getAllSubset("abc")
   i=0
   while i<len(result):
   print result[i]
   i+=1
   程序的运行结果为:
   a
   ab
   b
   ac
   abc
   bc
   c
   根据上述过程可知,第k次迭代的迭代次数为:2k-1。需要注意的是,n≥k≥1,迭代n次,总的遍历次数为:2n+1-(2+n),n≥1,所以,本方法的时间复杂度为O(2n)。
   由于在该算法中,下一次迭代过程都需要上一次迭代的结果,而最后一次迭代之后就没有下一次了。因此,假设原始集合有n个元素,则在迭代过程中,总共需要保存的子集个数为2n-1-1,n≥1。但需要注意的是,这里只考虑了子集的个数,每个子集元素的长度都被视为1。
   其实,比较上述两种方法,不难发现,第一种方法可以看作是用时间换空间,而第二种方法可以看作是用空间换时间。
【答案解析】[考点] 如何求集合的所有子集