暴力求解-子集生成
阿新 • • 發佈:2018-12-18
本篇介紹子集生成演算法:給定一個集合,列舉所有可能的子集。為了簡單起見,本節討論的集合彙總沒有重複元素。
輸入:整數n。
輸出:對於集合{0,1,.....n-1},列舉所有可能的子集。
執行結果:
增量構造法。
第一種思路是一次選出一個元素放到集合中。
void print_subset_add(int n,int *A,int cnt) { //增量構造法 //每次都選一個元素放入集合中 int i; for(i=0;i<cnt;i++) //列印當前集合 printf("%d ",A[i]); printf("\n"); int s=cnt?A[cnt-1]+1:0; //確定當期集合的最小值 for(i=s;i<n;i++) { A[cnt]=i; print_subset_add(n,A,cnt+1); //遞迴構造子集 } }
和前面不同,由於A中的元素個數不確定,每次遞迴呼叫都要輸出當前集合。另外,遞迴邊界也不需要顯示確定-如果無法繼續新增元素,自然就不會再遞迴了。
上面的程式碼用到了定序的技巧:規定集合A中所有元素的編號從小到大排列,就不會把集合{1,2}按照{1,2}和{2,1}輸出兩次了。
在列舉子集的增量法巨集中,需要使用定序的技巧,避免同一個集合列舉兩次。
位向量法。
第二種思路是構造一個位向量B[i],而不是直接構造子集A本身,其中B[i]=1,當且僅當i在子集A中,遞迴實現如下:
void print_subset_B(int n,int *B,int cnt) { //位向量法 //構造一個位向量B[i] 而不是直接構造子集A本身,其中B[i]=1當且僅當i在 //子集A中 if(cnt==n) { for(int i=0;i<cnt;i++) //列印當前集合 if(B[i]) printf("%d ",i); printf("\n"); } else { B[cnt]=1; print_subset_B(n,B,cnt+1); B[cnt]=0; print_subset_B(n,B,cnt+1); } }
必須當所有元素是否選擇全部確定完畢後才是一個完整的子集,因此仍然像以前那樣當 if(cur ==n )成立時才輸出。
二進位制法。
另外,還可以用二進位制來表示{0,1,2,....,n-1}的子集S:從右往左第i位(各位從0開始編號)表示元素i是否在集合S中。
為了處理方便,最右邊的位總是對應元素9,而不是元素1.
可以用二進位制表示子集,其中從右往左第i位(從0開始編號)表示元素i是否在集合中(1表示“在”,0表示“不在”)
此時僅表示出集合是不夠的,還需要對集合進行操作。幸運的是,常見的集合運算都可以用位運算子簡單實現。
當用二進位制表示子集時,位運算中的按位與,或,異或對應集合的交、並和對稱差。
這樣,不難用下面的程式輸出子集S對應的各個元素:
void print_subset_binary(int n,int s)
{
//二進位制法
for(int i=0;i<n;i++) //列印子集S
if(s&(1<<i))
printf("%d ",i);
printf("\n");
}
而列舉子集和列舉整數一樣簡單:
for(int i=0;i<(1<<n);i++) //列舉各子集對應的編碼0,1,....2^n-1;
print_subset_binary(n,i);
從程式碼量看,列舉子集的最簡單方法是二進位制法。