1. 程式人生 > >求一個集合的所有子集問題

求一個集合的所有子集問題

轉載請註明出處

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層做了取捨,所以這裡可以用遞迴了。整個過程其實就是對二叉樹的先序遍歷。

根據上面的思想,首先需要一個結構來儲存元素,這個”取捨”過程,其實就是線上性結構中的增加和刪除操作,很自然考慮用鏈式的儲存結構,所以我們先來實現一個連結串列:

typedef struct  LNode
{
	int data;
	LNode *next;
}LinkList;

//建立一個連結串列,你逆向輸入n個元素的值
int listCreate(LinkList *srcList, int number)
{
	LinkList *pTemp;
	int i = 0;
	srcList->next = NULL;
	srcList->data = 0;

	for (i = number; i > 0; --i)
	{
		pTemp = (LinkList *)malloc(sizeof(LNode));
		pTemp->data = i+20;//隨便賦值
		pTemp->next = srcList->next;
		srcList->next = pTemp;
	}
	return 0;
}

//銷燬一個連結串列
int listDestroy(LinkList *srcList)
{
	if (!srcList || !srcList->next)
	{
		return 0;
	}

	LinkList *p1 = srcList->next;
	LinkList *p2 = p1->next;

	do
	{
		free(p1);
		p1 = p2;
		if (p2 != NULL)
		{
			p2 = p2->next;
		}
	}while (p1);
	return 0;
}

//插入操作
//在strList第nIndex之前插入資料data
//nIndex最小為1
int listInsert(LinkList *srcList, int nIndex, int data)
{
	LinkList *pStart = srcList;
	int j = 0;
	if (nIndex < 1)
	{
		return 0;
	}
	while((pStart) && (j < nIndex-1))
	{
		pStart = pStart->next;
		j++;
	}
	if ((!pStart) || (j > nIndex-1))
	{
		return -1;//出錯
	}

	LinkList *temp = (LinkList *)malloc(sizeof(LNode));
	temp->data = data;
	temp->next = pStart->next;
	pStart->next = temp;
	return 0;
}

//刪除操作
//strList第nIndex位置的結點刪除,並通過data返回被刪的元素的值
//通常情況下返回的這個值是用不到的,不過這裡也保留備用
int listDelete(LinkList *srcList, int nIndex, int *data)
{
	LinkList *pStart = srcList;
	int j = 0;
	if (nIndex < 1)
	{
		return 0;
	}

	while((pStart) && (j < nIndex-1))
	{
		pStart = pStart->next;
		j++;
	}
	if ((!pStart) || (j > nIndex-1))
	{
		return -1;//出錯
	}
	LinkList *pTemp = pStart->next;
	pStart->next = pTemp->next;
	*data = pTemp->data;
	free(pTemp);

}
有了這個連結串列,遞迴演算法實現起來就很容易了:
//求冥集,nArray是存放n個元素的陣列
//首次呼叫i傳1,表示已對前面i-1個元素做了處理
void GetPowerSet(int nArray[], int nLength, int i, LinkList *outPut)
{
	int k = 0;
	int nTemp = 0;
	if (i >= nLength)
	{
		printList(*outPut);
	}
	else
	{
		k = listLength(outPut);
		listInsert(outPut, k+1, nArray[i]);
		GetPowerSet(nArray, nLength, i+1, outPut);
		listDelete(outPut, k+1, &nTemp);
		GetPowerSet(nArray, nLength, i+1, outPut);
	}

}


還有一種思想比較巧妙,可以叫按位對應法。如集合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(表示有,表示無,反之亦可),通過該整型數逐次增可遍歷獲取所有的數,即獲取集合的相應子集。

實現起來很容易:

void GetPowerSet2(int nArray[], int nLength)
{
	int mark = 0;
	int i = 0;
	int nStart = 0;
	int nEnd = (1 << nLength) -1;
	bool bNullSet = false;

	for (mark = nStart; mark <= nEnd; mark++)
	{
		bNullSet = true;
		for (i = 0; i < nLength; i++)
		{
			if (((1<<i)&mark) != 0) //該位有元素輸出
			{
				bNullSet = false;
				printf("%d\t", nArray[i]);
			}
		}
		if (bNullSet) //空集合
		{
			printf("@\t");
		}
		printf("\n");
	}
}


分析程式碼可以得出它的複雜度是O(n*2^n)。

程式碼下載地址:

https://github.com/pony-maggie/PowerSetDemo

http://download.csdn.net/detail/pony_maggie/7499161