ホームページへ戻る １１パズルの最長手数解へ戻る

最小完全ハッシュ関数の作り方

■順列型の最小完全ハッシュ関数

０から４までの５個の数字が下のように並んでいる場合を例にして説明します。

５個の数字の並べ方は ５！通り ありますので５！(=120)通りの並べ方の総てに対して 0から119 までの数値を一意に割り付けることが目的となります。



３４１０２



ここでは左側から順に数字を見ていくことにします。最初の数字は３で残りの数字の個数は４個ですね。

この 残れさた数字の個数分の総順列数 は４！ですが、この数量を 基数 と言います。

つまり左端の数字が何であるかを完全に識別する為に 最低限必要な基本となる重み のことです。

従って先ず最初の数字３に基数である４！を掛け算してはじき出します。



[ ３ ] ４１０２ → ３＊４！



次に左から２番目の数字ですが、ここから先はとても注意が必要です。

２番目の数字は４で残りの数字の個数は３個です。残りの数字の個数が３個なので基数は３！になります。つまり基数が変化します。この基数の変換が 無駄なハッシュ値領域を作らない 最小完全ハッシュ値を得る為の重要なポイントです。

そしてさらに重要なポイントは、先ほどの様にして４＊３！をはじき出すのは間違いだということです。

実際の数字そのものに基数の３！を掛けるのではなくて [４１０２] の４個の数字の中で４という数字が 何番目に小さな数字かという順位 に着目しなければならないのです。

０位から数えて数字４の順位は ３位 であるという考え方をして３＊３！をはじき出すのが正解なのです。

そうしてやらないと現在以降のはじき出す数値が前回の基数であった ４！に収まらなく なるのです。



[ ３４ ] １０２ → ３＊４！＋３＊３！



以下同様にして、



[ ３４１ ] ０２ → ３＊４！＋３＊３！＋１＊２！

[ ３４１０ ] ２ → ３＊４！＋３＊３！＋１＊２！＋０＊１！

[ ３４１０２ ] → ３＊４！＋３＊３！＋１＊２！＋０＊１！+０＊０！



この計算結果は９２でこれが ３４１０２ の最小完全ハッシュ値となります。



では次に、この考え方に基づいた最小完全ハッシュ関数の具体的な作り方に移ります。

階乗計算を毎回実行するのは無駄があるので、先ず 階乗値テーブル を作っておきます。 /* 階乗値テーブル */ int FACTOR[5] = { 24, 6, 2, 1, 1 }; 面倒なのは 「何番目に小さな数字かという順位」 を求める部分です。しかし、これにはとてもうまい方法が考案されています。

「着目している数字よりも大きな数字が、 まだ残っている数字群の中 にあればその値を １つ減ずる 」という処理をして行くと実際の数字がおのずと 順位の値に変化 してくれるというのです。



[] ３４１０２

[ ３ ] ３１０２ ←２番目の数字４は３より大きいので１つ減ずる。

[ ３３ ] １０２

[ ３３１ ] ０１ ←５番目の数字２は１より大きいので１つ減ずる。

[ ３３１０ ] ０ ←５番目の数字１は０より大きいので１つ減ずる。

[ ３３１００ ]



あとは単純に３＊４！＋３＊３！＋１＊２！＋０＊１！+０＊０！＝９２の計算をすることになります。

最後の項の値は必ずゼロ になることを考慮してこのようなプログラムが書けます。 /* 順列型の最小完全ハッシュ関数 */ int ChangeNumber(int table[]) { int i, j, hash = 0, work[SIZE-1]; for (i=0; i < SIZE-1; i++) work[i] = table[i]; for (i=0; i < SIZE-1; i++) { hash += work[i] * FACTOR[i]; for (j=i+1; j < SIZE-1; j++) if (work[i] < work[j]) work[j]--; } return hash; } ■組合せ型の最小完全ハッシュ関数

５個の中から２個を選ぶという設定で数字が下のように並んでいる場合を例にして説明します。

５個の中から２個を選ぶ組合せは 5 Ｃ 2 通り ありますので 5 Ｃ 2 (=10)通りの総ての選び方に対して 0から9 までの数値を一意に割り付けることが目的となります。



１０１００



先ほどと同様にして左側から順に数字を見ていくことにします。

先ず最初の位置の基数を考えます。ここがゼロであったとすれば 残りの組合せは 4 Ｃ 2 となります。

つまり、これだけの数値の幅を確保しておく必要があるということなのでこれが最初の位置の基数値になります。

そしてここが１なのでここで 4 Ｃ 2 をはじき出します。



[ １ ] ０１００ → 4 Ｃ 2



２番目の数字は０なので考える必要はありません。３番目の数字は１なのでここでの基数を考えます。

この位置で確保しておく必要がある数値の幅は 2 Ｃ 1 なのでその基数である 2 Ｃ 1 をはじき出します。



[ １０１ ] ００ → 4 Ｃ 2 ＋ 2 Ｃ 1



残りは総て０なのでここで計算は終りとなります。

この計算結果は８でこれが １０１００ の最小完全ハッシュ値となります。



順列型よりも基数が動的に変化するのでちょっと理解しにくいかも知れませんが手続きは順列型よりずっと簡単です。

プログラムも n Ｃ r の値を格納したテーブル PASCAL[n][r] を用意しておくとこのようになります。 /* 組合せ型の最小完全ハッシュ関数 */ int ChangeNumber(int table[]) { int i, n, r, hash = 0; n = SIZE - 1; r = 2; for (i=0; n >= r && r > 0; i++,n--) if (table[i]) hash += PASCAL[n][r--]; retuen hash; } ここでfor文の終了判定式ですが、n < r は残りが総て１で、r == 0 は残りが総て０である場合を意味します。

つまり選択の余地が無くなった時点で計算は終了します。判りにくければこのfor文を、 for (i=0; i < SIZE; i++,n--) としても同じ結果になります。

ところで、 n Ｃ r の値を格納したテーブル名が何故PASCALなのかと言うとこのテーブルの中身は パスカルの三角形 (２項定理の係数)そのものになっているからです。ちなみに、このテーブルは足し算だけで作ることが出来ます。 /* nＣr値テーブル */ int PASCAL[5][3] = { 1, 0, 0, 1, 1, 0, 1, 2, 1, 1, 3, 3, 1, 4, 6 }; ホームページへ戻る １１パズルの最長手数解へ戻る

