求一个集合的所有子集问题

来源:互联网 发布:软件设计师考试冲刺 编辑:程序博客网 时间:2024/05/21 14:58

转载请注明出处

http://blog.csdn.net/pony_maggie/article/details/31042651


作者:小马


一个包含n个元素的集合,求它的所有子集。比如集合A= {1,2,3}, 它的所有子集是:

{ {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3}, @}(@表示空集)。

 

这种问题一般有两种思路,先说说第一种,递归。递归肯定要基于一个归纳法的思想,这个思想用到了二叉树的遍历,如下图所示:

 


可以这样理解这张图,从集合A的每个元素自身分析,它只有两种状态,或是某个子集的元素,或是不属于任何子集,所以求子集的过程就可以看成对每个元素进行“取舍”的过程。上图中,根结点是初始状态,叶子结点是终结状态,该状态下的8个叶子结点就表示集合A的8个子集。第i层(i=1,2,3…n)表示已对前面i-1层做了取舍,所以这里可以用递归了。整个过程其实就是对二叉树的先序遍历。

 

根据上面的思想,首先需要一个结构来存储元素,这个”取舍”过程,其实就是在线性结构中的增加和删除操作,很自然考虑用链式的存储结构,所以我们先来实现一个链表:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. typedef struct  LNode  
  2. {  
  3.     int data;  
  4.     LNode *next;  
  5. }LinkList;  
  6.   
  7. //建立一个链表,你逆向输入n个元素的值  
  8. int listCreate(LinkList *srcList, int number)  
  9. {  
  10.     LinkList *pTemp;  
  11.     int i = 0;  
  12.     srcList->next = NULL;  
  13.     srcList->data = 0;  
  14.   
  15.     for (i = number; i > 0; --i)  
  16.     {  
  17.         pTemp = (LinkList *)malloc(sizeof(LNode));  
  18.         pTemp->data = i+20;//随便赋值  
  19.         pTemp->next = srcList->next;  
  20.         srcList->next = pTemp;  
  21.     }  
  22.     return 0;  
  23. }  
  24.   
  25. //销毁一个链表  
  26. int listDestroy(LinkList *srcList)  
  27. {  
  28.     if (!srcList || !srcList->next)  
  29.     {  
  30.         return 0;  
  31.     }  
  32.   
  33.     LinkList *p1 = srcList->next;  
  34.     LinkList *p2 = p1->next;  
  35.   
  36.     do  
  37.     {  
  38.         free(p1);  
  39.         p1 = p2;  
  40.         if (p2 != NULL)  
  41.         {  
  42.             p2 = p2->next;  
  43.         }  
  44.     }while (p1);  
  45.     return 0;  
  46. }  
  47.   
  48. //插入操作  
  49. //在strList第nIndex之前插入数据data  
  50. //nIndex最小为1  
  51. int listInsert(LinkList *srcList, int nIndex, int data)  
  52. {  
  53.     LinkList *pStart = srcList;  
  54.     int j = 0;  
  55.     if (nIndex < 1)  
  56.     {  
  57.         return 0;  
  58.     }  
  59.     while((pStart) && (j < nIndex-1))  
  60.     {  
  61.         pStart = pStart->next;  
  62.         j++;  
  63.     }  
  64.     if ((!pStart) || (j > nIndex-1))  
  65.     {  
  66.         return -1;//出错  
  67.     }  
  68.   
  69.     LinkList *temp = (LinkList *)malloc(sizeof(LNode));  
  70.     temp->data = data;  
  71.     temp->next = pStart->next;  
  72.     pStart->next = temp;  
  73.     return 0;  
  74. }  
  75.   
  76. //删除操作  
  77. //strList第nIndex位置的结点删除,并通过data返回被删的元素的值  
  78. //通常情况下返回的这个值是用不到的,不过这里也保留备用  
  79. int listDelete(LinkList *srcList, int nIndex, int *data)  
  80. {  
  81.     LinkList *pStart = srcList;  
  82.     int j = 0;  
  83.     if (nIndex < 1)  
  84.     {  
  85.         return 0;  
  86.     }  
  87.   
  88.     while((pStart) && (j < nIndex-1))  
  89.     {  
  90.         pStart = pStart->next;  
  91.         j++;  
  92.     }  
  93.     if ((!pStart) || (j > nIndex-1))  
  94.     {  
  95.         return -1;//出错  
  96.     }  
  97.     LinkList *pTemp = pStart->next;  
  98.     pStart->next = pTemp->next;  
  99.     *data = pTemp->data;  
  100.     free(pTemp);  
  101.   
  102. }  


有了这个链表,递归算法实现起来就很容易了:
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //求冥集,nArray是存放n个元素的数组  
  2. //首次调用i传1,表示已对前面i-1个元素做了处理  
  3. void GetPowerSet(int nArray[], int nLength, int i, LinkList *outPut)  
  4. {  
  5.     int k = 0;  
  6.     int nTemp = 0;  
  7.     if (i >= nLength)  
  8.     {  
  9.         printList(*outPut);  
  10.     }  
  11.     else  
  12.     {  
  13.         k = listLength(outPut);  
  14.         listInsert(outPut, k+1, nArray[i]);  
  15.         GetPowerSet(nArray, nLength, i+1, outPut);  
  16.         listDelete(outPut, k+1, &nTemp);  
  17.         GetPowerSet(nArray, nLength, i+1, outPut);  
  18.     }  
  19.   
  20. }  


还有一种思想比较巧妙,可以叫按位对应法。如集合A={a,b,c},对于任意一个元素,在每个子集中,要么存在,要么不存在

映射为子集:

(a,b,c)

(1,1,1)->(a,b,c)

(1,1,0)->(a,b)

(1,0,1)->(a,c)

(1,0,0)->(a)

(0,1,1)->(b,c)

(0,1,0)->(b)

(0,0,1)->(c)

(0,0,0)->@(@表示空集)

观察以上规律,与计算机中数据存储方式相似,故可以通过一个整型数与集合映射...000 ~ 111...111(表示有,表示无,反之亦可),通过该整型数逐次增可遍历获取所有的数,即获取集合的相应子集。

实现起来很容易:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void GetPowerSet2(int nArray[], int nLength)  
  2. {  
  3.     int mark = 0;  
  4.     int i = 0;  
  5.     int nStart = 0;  
  6.     int nEnd = (1 << nLength) -1;  
  7.     bool bNullSet = false;  
  8.   
  9.     for (mark = nStart; mark <= nEnd; mark++)  
  10.     {  
  11.         bNullSet = true;  
  12.         for (i = 0; i < nLength; i++)  
  13.         {  
  14.             if (((1<<i)&mark) != 0) //该位有元素输出  
  15.             {  
  16.                 bNullSet = false;  
  17.                 printf("%d\t", nArray[i]);  
  18.             }  
  19.         }  
  20.         if (bNullSet) //空集合  
  21.         {  
  22.             printf("@\t");  
  23.         }  
  24.         printf("\n");  
  25.     }  
  26. }  


分析代码可以得出它的复杂度是O(n*2^n)。

 


0 0