Ark's Blog

𝔇eadline-𝔇riven 𝔇evelopment

ようこそ

bitによる部分集合の列挙 と 数学的理解

bitテクニックで、集合の部分集合を列挙するものがあります。その紹介と数学的理解です。

bitによる部分集合の列挙

集合uint Sに対して、Sの部分集合Tは次のようにして全列挙できます。

uint S; // <- 集合
for (uint T=S; ; T=(T-1)&S) {
    // 処理
    if (T==0) break;
}

例えばS0b100101のときは、Tforの中で、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(2n2n)=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) になります。なぜなら、STの組合せの個数は、 S=02n12popcount(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)i=1n,B=(bi)i=1n2nに対し辞書式順序:

AB:A=B(aj<bjwherej=min{i{1,,n}aibi})
で定めたとき全順序集合(2n,)が与えられる。有限集合かつ全順序集合なので演算minmaxが自然に定まる。最小元をO:=min(2n)=(0)i=1nとおく。

2n上の二項関係を、A=(ai)i=1n,B=(bi)i=1n2nに対し

AB:i{1,,n},aibi

で定義する。これはABを集合としてみたときの部分集合の関係に相当する。よって便宜上、ABが成り立つとき「ABの部分集合」ということとする。

2n上の二項演算を、A=(ai)i=1n,B=(bi)i=1n2nに対し

AB:=(min{si,ti})i=1n
で定義する。これはABを集合としてみたときの共通部分に相当する。

写像dec:2n{O}2nA2n{O}に対して

dec(A):=max{B2nB<A}
で定義する。dec(A)は、辞書式順序でAの次に小さいものを表す。

対応付け

以上の定義から、bitによる集合表現と演算は次のように対応付けられる。

  • uint Sによる集合: S=(si)i=1n2n
  • uint Tuint Sの部分集合: TS
  • S & Tの演算結果: ST2n
  • S - 1の演算結果: dec(S)2n
    • ここで、この演算はO2nに対しては定義されていないことに注意

定義2

集合S2nを固定する。

2n上の列:

S=S1>S2>>Sk=Owhere{S1,S2,,Sk}={A2nAS}
とする。つまり、列(Si)i=1kは、Sの部分集合全体を降順に並べた列である。

SiSi+1に対して、定義よりSi>A>Si+1となるような部分集合ASは存在しないので

Si+1=max{ASA<Si}
が成り立つ。

さらに2n上の列(Ti)i=1を次のように帰納的に定める:

  • T1:=S
  • TiOならばTi+1:=dec(Ti)S
  • Ti=OならばTi+1:=O

(Ti)i=1は、上のbitテクニックによって生成されるuint Tの値の列に対応する。

命題

次が成り立つ。

(Si)i=1k=(Ti)i=1k
これは、上のbitテクニックによる部分集合の列挙が正しいことを表している。

証明

S1=T1=S は明らかに成り立つので、Si=TiOSi+1=Ti+1 が成り立つことを示せば帰納法より十分。

Si=TiOが成り立つと仮定すると

Ti+1=dec(Ti)STi+1の定義=max{A2nA(dec(Ti)S)}XYXY=max{A2nA(max{B2nB<Ti}S)}decの定義=max{A2nAmax{BSB<Ti}}B<TiBS<Ti=max{A2nA<TiAS}=max{ASA<Ti}=max{ASA<Si}Ti=Si=Si+1Si+1の性質

が成り立つ。

以上より、(Si)i=1k=(Ti)i=1kが成り立つ。

感想とか

  • bitによる集合の表現は、集合{1,,n}上の部分集合を表すもの
  • 2{1,,n}2nと同一視できる
  • 2ブール代数における演算はbit演算と対応する

大事な部分を抽出するとこんな感じでしょうか。

「代数の概念を用いて対象を同一視して捉えると、ものごとがすんなり理解できる」というのが最近の思うところです。同一視さいこう。

余談ですが、bitテクニックは調べるとおもしろいものがたくさんあってたのしいです。これとか、初めて見たときは感動しました。

参考文献

スターをつけました

引用をストックしました

引用するにはまずログインしてください

引用をストックできませんでした。再度お試しください

限定公開記事のため引用できません。

読者です 読者をやめる 読者になる 読者になる
11