集合枚舉子集-學習筆記


集合枚舉子集-學習筆記

算法

有一個集合,請輸出它的所有子集。

子集,即為被這個這個集合包括的所有集合,包括空集。那么顯然,假如有 \(n\) 個元素,那么有 \(2^n\) 個子集。如何枚舉子集呢?

首先有一個顯然的方法:用 \(2^n\) 的 dfs 枚舉。但這樣有弊端:時空較大,而且比較麻煩。比如一個動態規划,你每次轉移都要跑一遍 dfs ,時空開銷很大,而且處理不方便。那么就有另外一種方法出現:循環枚舉。

首先放代碼:

for(int j=st;j;j=(j-1)&st) j2=st^j;

st 就是要枚舉的集合,j 就是子集,j2 就是 j 相對於 st 的補集。這里我們認為集合是一個二進制數中所有元素 \(1\) ,一個 \(1\) 代表一個元素。這樣為什么是正確的呢?

首先,為了保證枚舉出來的元素都是原式的子集,我們用了 &st 這個位運算操作,因此可以保證。

其次,因為減法和按位與都是不增操作,因此可以保證不重。

那如何保證不漏呢?

模擬一遍過程:st =1101,我們按照循環順序模擬過程。

\(【1】1101\)

\(【2】1100\)

\(【3】1001\)

\(【4】1000\)

\(【5】0101\)

\(【6】0100\)

\(【7】0001\)

我們假定 \(0000\) 也在元素中,然后按照一定規律排列子集:

\[【1】1101\;\;\; 【5】0101 \]

\[【2】1100\;\;\; 【6】0100 \]

\[【3】1001\;\;\; 【7】0001 \]

\[【4】1000\;\;\; 【8】0000 \]

有沒有發現規律?每一行,后三位都是相同的。所以這個算法的本質算是,對於每一個 \(1\) ,先把這一位設定為 \(1\) 枚舉后面所有位,再把這一位設定為 \(0\) 枚舉一遍,然后再往前面遞歸。因為減 \(1\) 可以看做把最后一個 \(1\) 設定為 \(0\) ,后面所有設定為 \(1\)。這里建議還是自己手玩一下,找一找規律。所以這個算法類似於二進制枚舉,考慮到了所有情況。

這個做法是從大到小枚舉子集,着實十分優美。但要注意的是這種辦法沒法枚舉空集,需要手動計算。

枚舉子集在一些位運算的題目中可能會涉及,而這種方法可以很好地減少代碼復雜度以及時空復雜度,同時也是一種很好的思想。

復雜度證明

空間不管。

時間的話,如果一個集合元素個數為 \(x\) ,復雜度為 \(O(2^x)\)

有一種特殊情況:枚舉 \(0\)\(2^n-1\) 的所有數的所有子集,此時復雜度即為:

\[\sum_{k=0}^nC_n^k\times 2^k \]

相當於從 \(n\) 個位上選出 \(k\) 個位有值,然后有 \(C_n^k\) 種方案,每種方案 \(2^k\)

根據二項式定理:

\[(a+b)^n=\sum_{i=0}^nC_n^ia^ib^{n-i} \]

可得:原式等價於:

\[\sum_{k=0}^nC_n^k\times1^{n-k}\times2^k \]

等價於 \(3^n\) 。復雜度還算是比較優秀的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM