bitテクニックで、集合の部分集合を列挙するものがあります。その紹介と数学的理解です。
bitによる部分集合の列挙
集合uint S
に対して、S
の部分集合T
は次のようにして全列挙できます。
uint S;
for (uint T=S; ; T=(T-1)&S) {
if (T==0) break;
}
例えばS
が0b100101
のときは、T
はfor
の中で、0b100101
0b100100
0b100001
0b100000
0b101
0b100
0b1
0b0
の順に変化します。
ちゃんとS
の部分集合すべてが降順で列挙できてることがわかります。
ちなみにこのテクニックは蟻本に載ってました。
何がうれしいの
大きさn
の集合S
と、S
の部分集合T
を全列挙しようとしたときに単純な実装だと次のようになります。
for(uint S=0; S<1<<n; S++) {
for(uint T=0; T<1<<n; T++) {
if ((T&S) != T) continue;
}
}
これだと全体の計算量は O(2n⋅2n)=O(4n) になりますが、上述のbitテクニックを使うと
for(uint S=0; S<1<<n; S++) {
for (uint T=S; ; T=(T-1)&S) {
if (T==0) break;
}
}
となり O(3n) になります。なぜなら、S
とT
の組合せの個数は、
∑S=02n−12popcount(S)=∑i=0nnCi2i=(2+1)n=3n通りだからです。
O(4n)からO(3n)に落ちるのは地味に嬉しい。
このテクニックが有効な問題例:
直感的理解
(T-1)&S
は「T
の次に辞書順で小さい集合とS
の共通部分」である。これは「S
の部分集合全体の上でT
の次に辞書順で小さい集合」をとる操作になっているため、「T
より辞書順で小さいS
の部分集合のうち辞書順最大の集合」となる。
よって降順ですべての部分集合が生成され、最終的に0b0
になってループが終了する。
数学的理解
定義1
2:={0,1}とおき、nを適当な正の整数とする。
2n上の二項関係≤を、A=(ai)ni=1,B=(bi)ni=1∈2nに対し辞書式順序:
A≤B:⟺A=B∨(aj<bjwherej=min{i∈{1,⋯,n}∣ai≠bi})
で定めたとき全順序集合
(2n,≤)が与えられる。有限集合かつ全順序集合なので演算
min、
maxが自然に定まる。最小元を
O:=min(2n)=(0)ni=1とおく。
2n上の二項関係⊂を、A=(ai)ni=1,B=(bi)ni=1∈2nに対し
A⊂B:⟺∀i∈{1,⋯,n},ai≤bi
で定義する。これはAとBを集合としてみたときの部分集合の関係に相当する。よって便宜上、A⊂Bが成り立つとき「AはBの部分集合」ということとする。
2n上の二項演算∩を、A=(ai)ni=1,B=(bi)ni=1∈2nに対し
A∩B:=(min{si,ti})ni=1
で定義する。これは
Aと
Bを集合としてみたときの共通部分に相当する。
写像dec:2n−{O}→2nをA∈2n−{O}に対して
dec(A):=max{B∈2n∣B<A}
で定義する。
dec(A)は、辞書式順序で
Aの次に小さいものを表す。
対応付け
以上の定義から、bitによる集合表現と演算は次のように対応付けられる。
uint S
による集合: S=(si)ni=1∈2n
uint T
はuint S
の部分集合: T⊂S
S & T
の演算結果: S∩T∈2n
S - 1
の演算結果: dec(S)∈2n
- ここで、この演算はO∈2nに対しては定義されていないことに注意
定義2
集合S∈2nを固定する。
2n上の列:
S=S1>S2>⋯>Sk=Owhere{S1,S2,⋯,Sk}={A∈2n∣A⊂S}
とする。つまり、列
(Si)ki=1は、
Sの部分集合全体を降順に並べた列である。
SiとSi+1に対して、定義よりSi>A>Si+1となるような部分集合A⊂Sは存在しないので
Si+1=max{A⊂S∣A<Si}
が成り立つ。
さらに2n上の列(Ti)∞i=1を次のように帰納的に定める:
- T1:=S
- Ti≠OならばTi+1:=dec(Ti)∩S
- Ti=OならばTi+1:=O
列(Ti)∞i=1は、上のbitテクニックによって生成されるuint T
の値の列に対応する。
命題
次が成り立つ。
(Si)ki=1=(Ti)ki=1
これは、上のbitテクニックによる部分集合の列挙が正しいことを表している。
証明
S1=T1=S は明らかに成り立つので、Si=Ti≠O⇒Si+1=Ti+1 が成り立つことを示せば帰納法より十分。
Si=Ti≠Oが成り立つと仮定すると
Ti+1=dec(Ti)∩S=max{A∈2n∣A⊂(dec(Ti)∩S)}=max{A∈2n∣A⊂(max{B∈2n∣B<Ti}∩S)}=max{A∈2n∣A⊂max{B⊂S∣B<Ti}}=max{A∈2n∣A<Ti∧A⊂S}=max{A⊂S∣A<Ti}=max{A⊂S∣A<Si}=Si+1∵Ti+1の定義∵X⊂Y⇒X≤Y∵decの定義∵B<Ti⇒B∩S<Ti∵Ti=Si∵Si+1の性質
が成り立つ。
以上より、(Si)ki=1=(Ti)ki=1が成り立つ。
感想とか
- bitによる集合の表現は、集合{1,⋯,n}上の部分集合を表すもの
- 2{1,⋯,n}は2nと同一視できる
- 2のブール代数における演算はbit演算と対応する
大事な部分を抽出するとこんな感じでしょうか。
「代数の概念を用いて対象を同一視して捉えると、ものごとがすんなり理解できる」というのが最近の思うところです。同一視さいこう。
余談ですが、bitテクニックは調べるとおもしろいものがたくさんあってたのしいです。これとか、初めて見たときは感動しました。
参考文献