*目次 [#j7e5b518]

#contents


*圧縮後のバイナリコードの設計 [#z6af1f91]

 例えば、100,000文字のデータファイルを圧縮したいとする。このとき、a〜fの各アルファベットの頻出回数は次のようになったとする。

||a|b|c|d|e|f|
|回数(千単位)|45|13|12|16|9|5|

 各アルファベットのバイナリコードとして、固定長のバイナリコードに変換するのが自然な方法である。一方、頻出回数が多いアルファベットに対して、短いバイナリコードをうまく対応させれば、バイナリ変換後の総文字コード数は固定長の場合に比べて少なくなる。つまり、圧縮できているとことである。

||a|b|c|d|e|f|
|固定長のバイナリコード|000|001|010|011|100|101|
|可変長のバイナリコード|0|101|100|111|1101|1100|


**固定長を採用した場合 [#ua448d06]

 a〜fを上記の表のように3ビットの固定長のバイナリコードに対応させたとする。この方式を採用したとき、ファイル全体として300,000ビットになる。


**可変長を採用した場合 [#k586c9b3]

 101は4ビットのバイナリコードに対応されるが、最も頻出頻度が高い0は1ビットのバイナリなりコードに対応される。バイナリ変換後の総文字コード数は次のように計算できる。

&mimetex("(45 \times 1 + 13 \times 3 + 12 \times 3 + 16 \times 3 + 9 \times 4 + 5 \times 4 ) \cdot 1,000 = 224,000 [bit]");

 固定長時は300,000ビットであったが、可変長時は224,000ビットになった。よって、圧縮率は約0.75(=224,000/300,000)になる。つまり、25%分だけ圧縮できたことを意味する。


*ハフマン符号 [#b2580aa0]

 可変長を採用した場合のバイナリコードは''ハフマン符号(Huffman code)''と呼ばれたものがある。

 ハフマン木を用いて最も効率的なバイナリコードが割り当てられる。割り当て用のバイナリコードの作成手順は次の通りである。

+各記号を生起確率の降順に並べる。ただし、同じ生起確率を持つ記号はどちらが先でも構わない。
+生起確率の最も小さい記号とその次に小さい記号を葉として、新たな節点を設ける。その節点にそれら2つの記号の生起確率の合計を与える。その節点から生起確率が小さい記号に向かう枝に1というラベルを、もう片方の枝に0というラベルを与える。
+ステップ2で作成した節点を新たな記号と見なして、新たに節点を作ることができなくなるまでステップ2を繰り返す。
+最上位の節点(根)から各記号に至る各枝に与えられたラベルの系列が、その記号のハフマン記号である。

例1:記号列aaaabbc(aが4文字、bが2文字、cが1文字)に対するハフマン木は次のようになる。

#img(http://security2600.sakura.ne.jp/main2/image3/huffman1.jpg)
#img(,clear)

 このハフマン木から、各アルファベットのハフマン符号を導ける(上から枝のラベルを見ていけばよい)。

-aのハフマン符号:0
-bのハフマン符号:10
-cのハフマン符号:11

 また、各アルファベットの生起確率は次のようになる。

-aの生起確率:P(a)=4/7
-bの生起確率:P(b)=2/7
-cの生起確率:P(c)=1/7

 ハフマン符号と生起確率を比較すると、生起確率の高いaには短いバイナリコードである0が割り当てられたことが確認できるはずだ。

例2:記号列xyyyyzzzzuuuuuvvvvvv(xが1文字、y,zが4文字、uが5文字、vが6文字)に対するハフマン木は次のようになる。葉から節を作るときは、最小のものと2番目に小さいものから作ることに注意。よって、9/20を作った次は、6/20と5/20から11/20を作らなければならない。

#img(http://security2600.sakura.ne.jp/main2/image3/huffman2.jpg)
#img(,clear)

-xのハフマン符号:101
-yのハフマン符号:100
-zのハフマン符号:11
-uのハフマン符号:01
-vのハフマン符号:00

[補講]可変長のバイナリコードの場合は、ハフマン木が採用される。一方、固定長のバイナリコードの場合は、[[完全2分木]]が採用される。だから、各アルファベットのバイナリコードは同じビット長を持つわけである。

 もし、例1のabcという文字列をまとめて圧縮したときのバイナリコード列は、01011(=0・10・11)になる。このように変換することが''エンコード''である。一方、符合化された記号列を元の記号列に戻すことを''デコード(復号)''という。静的ハフマン符号の手法では、復号のために記号と符号の対応表が必要である。


**ハフマンアルゴリズム(Huffman's algorithm) [#u0fea6fd]

 各記号を次のように定義する。

-C
--n個の文字の集合
-c
--Cの要素(c∈C)。即ち、Cに含まれる文字のこと。
-f(c)
--cの頻出頻度、即ちcの生起確率
-Q
--[[優先待ち行列]](priority queue)
---優先度の大きいものから要素を取りだす仕組みになっているデータ構造。

 ハフマン木を生成するハフマンアルゴリズムの主要関数Huffman(C)は次のように構成される。

#code(pascal){{
n←|C|
Q←C
for i←1 to n-1
	z←Allocate-Node()
	left[z]←Extract-Min(Q)
	x←left[z]
	right[z]←Extract-Min(Q)
	y←right[z]
	f[z]←f[x]+f[y]
	Insert(Q,z)
return Extract-Min(Q)

}}

 2行目で、Cに含まれる文字たちをQにセットする。~
 for文で最小値と2番目に小さい値をQから取り出して、それぞれをx,yにセットする。そして、x,yの生起確率の和をzの生起確率としてセットする(zはあらかじめノードとして定義しておいた)。そのzをQに挿入する。これをn-1回繰り返す。~
 最終的にハフマン木の根を返している。

 最後に計算量を分析する。~
 2行目のQの初期化において、QはバイナリヒープなのでBuild-Heapアルゴリズムを実行する。よって、&mimetex("O(n)");回になる。~
 forループの中の各ヒープの操作に&mimetex("O(\log n)");回必要なので、forループ全体で見るとこれを|n|-1回繰り返す。よって、&mimetex("n \log n");回になる。~
 総合すると、n個の文字の集合Cを処理するためには&mimetex("n \log n");回必要である。


***ハフマンアルゴリズムの完全性(correctness) [#g12e2c11]

[補題1]~
Cの中で頻度回数が1番目・2番目に小さいものをそれぞれx,yとする。~
このとき、「同じビット長かつ最後のビットだけが異なる」ようなx,yのcodewordを含むoptimal prefix codeが存在する。

[補講2]~
Tを、C上のoptimal prefix codeを表す完全2部木とする。また、f[c]をc∈Cの頻度回数とする。~
Tの兄弟葉である2つのx,y、x,yの親としてzを考える。~
このとき、f[z]=f[x]+f[y]の頻度回数を持つ文字としてzを考えると、木T'=T-{x,y}はC'=C-{x,y}∪{z}のoptimal prefix codeを示す。

[定理]~
Huffmanはoptima prefix codeを生成する。

[証明]補題1と2より、成り立つ。 □

*ハフマン符号における圧縮率 [#dd7639a8]

 圧縮率の定義から、ハフマン符号における圧縮率の公式を作ることができる。このとき、記号と符号の対応表の存在を忘れてはいけない。

(圧縮率)={(ハフマン符号化された記号列のビット長)+(記号と符号の対応表のビット数)}/(原記号列のビット数)

 なお、「記号と符号の対応表のビット数」の長さは、記号をM種類とすると、約&mimetex("M \log_2 M +2M");ビットになる。


*参考文献 [#kb01394f]

-『INTRODUCTION TO ALGORITHMS』
-『ソフトウェア開発技術者 大滝みや子先生のアルゴリズム教科書』
-『ソフトウェア開発技術者試験対策 コンピュータサイエンス補足資料+午後演習問題』