図解DEFLATEアルゴリズム 1/2 ー ハフマン符号とLZ77アルゴリズム
DEFLATEアルゴリズムの概要
DEFLATEはzipやgzip, PNGファイルなどにおいて使用される圧縮アルゴリズムです。
DEFLATEではまずLZ77で圧縮を行い、その後にハフマン符号化で圧縮するという流れで圧縮が行われます。
この記事では第一回としてハフマン符号やLZ77とは何かについて解説していきます。
ハフマン符号について
ハフマン符号化は、データ圧縮に使用される効率的な符号化手法の一つです。
この手法は頻度が高いデータは短いコードを、頻度が低いデータは長いコードを割り当てます。
この符号はハフマン木というデータ構造に基づいて決定されます。
ハフマン木
ハフマン木は、ハフマン符号化に使用されるデータ構造です。これは、出現頻度に基づいてデータを効率的に符号化するための二分木です。
ハフマン木を構成するためのアルゴリズム
ハフマン木を作成するための手順は以下の通りです:
-
まず、各文字とその頻度をリストアップします。これらは、ハフマン木の初期ノードとなります。
-
次に、最も頻度が低い2つのノードを選び、それらを組み合わせて新しいノードを作成します。この新しいノードの頻度は元の2つのノードの頻度の合計となります。
-
このプロセスを繰り返し、全てのノードが一つの木になるまで続けます。
-
最終的に得られるハフマン木から、各データ項目に対するハフマンコードを生成できます。木のルートから各データ項目までのパスをたどり、左枝を'0'、右枝を'1'として符号化します。
ハフマン木の例
入力DAEBCBACBBBC
に対して上記のアルゴリズムを適用すると、以下のハフマン木を構築できます。
参考 ハフマン符号
各文字の出現頻度と割り当てられた符号は以下の通りです。
文字 | 個数 | 符号 |
---|---|---|
B | 5 | 0 |
C | 3 | 10 |
A | 2 | 110 |
D | 1 | 1110 |
E | 1 | 1111 |
個数の多い文字ほど短い符号が割り当てられていることがわかります。
データサイズの比較
固定長で符号化した場合1文字あたり最低3bit必要になります。以下のように符号化してみましょう。
A B C D E
000 001 010 011 100
メッセージ全体は以下のようになり、全体は36bitです。
D A E B C B A C B B B C
011 000 100 001 010 001 000 010 001 001 001 010
上記の表を使ってハフマン符号で符号化すると以下のようになります。
D A E B C B A C B B B C
1110 110 1111 0 10 0 110 10 0 0 0 10
全体は25bitであり、固定長の符号化と比べてサイズが小さいことが確認できます。
LZ77アルゴリズムについて
次にLZ77アルゴリズムについて解説していきます。
LZ77はランレングス法という圧縮方式をベースにして開発されたアルゴリズムです。
ランレングス法は以下の図のように同じ値が何回連続して出現するかを記録する手法です。
LZ77アルゴリズムの圧縮ステップ
LZ77はランレングス法を発展させたアルゴリズムです。単一の文字の繰り返しだけでなく、文字列の繰りかえしを圧縮することが可能です。
スライディングウィンドウ
LZ77アルゴリズムでは、スライディングウィンドウという概念が重要です。これは、走査している文字列の現在位置の前後にある特定の長さの文字列集合を指します。
スライディングウィンドウは参照部、符号化部の二つの部分に分けられます。
参照部はすでに圧縮が完了した部分です。符号化部はこれから圧縮しようとしている部分です。
各ステップでは、参照部内で符号化部の文字列と最長に一致するパターンを探し出し、 そのパターンの参照部の最後尾からのオフセット、一致する長さ、そしてパターンに一致しなくなる最初の文字(不一致記号)を結果に追加します。
「オフセット」と「長さ」が共に0である場合、それは参照部中に一致する文字列パターンが存在しなかったことを示します。
また、参照部のパターンとの一致は繰り返しを許容します。 例えば、参照部がab
で、走査している符号化部がababac
であった場合、ab
という文字列パターンがababa
と5文字分繰り返されていると解釈します。
具体例
文章だけでは理解しにくいかもしれませんので、具体的な例を用いて説明します。上図のaacaacabcabaac
という文字列を長さ6のスライディングウィンドウを用いたLZ77アルゴリズムで圧縮する手順を見てみましょう。この手順は以下の通りです。
- 最初の文字は
a
です。参照部は空なので一致するパターンはありません。
そのため、結果に(0,0,a)を追加します。
- 参照部は
a
で、符号化部はacaa
です。
符号化部先頭のa
だけが一致します。この一致する長さは1で、参照部最後尾からのa
パターンのオフセットは1です。
不一致記号はc
なので、結果に(1,1,c)を追加します。
- 参照部は
aac
で、符号化部はaaca
です。
aaca
という部分がaac
というパターンの4文字分繰り返しとなっています。
符号化部の長さが4であるため、aaca
が最長の一致する文字列となります。aac
パターンの最後尾からのオフセットは3なので、結果に(3,4,b)を追加します。
- 参照部は
caacab
で、符号化部はcaba
です。
cab
というパターンが最も長く一致します。
この一致する長さは3で、このパターンの参照部最後尾からのオフセットも3です。
そのため、結果に(3,3,a)を追加します。
- 参照部
abcaba
で、符号化部はaac
です。
a
というパターンが2文字分繰り返されています。
このパターンの参照部最後尾からのオフセットは1なので、結果に(1,2,c)を追加します。
- 最終的な出力は(0,0,a),(1,1,c),(3,4,b),(3,3,a),(1,2,c)となります。
以上がLZアルゴリズムによる圧縮手順となります。
LZ77アルゴリズムの解凍ステップ
解凍ではタプル内の情報を用いて参照部内の一致するパターンを探し出し、そのパターンと次に一致しない文字を用いて元の文字列を再構築しています。
先ほど圧縮した結果である(0,0,a),(1,1,c),(3,4,b),(3,3,a),(1,2,c)を解凍する手順を1ステップずつ図解していきます。
- 最初のタプルは(0,0,a)です。参照部は空です。マッチする文字列パターンはありません。
解凍結果にa
をセットし、参照部をa
に更新します。
- 次のタプルは(1,1,c)です。参照部最後尾から1文字前のパターン
a
が1文字分繰り返され、次の文字がc
であることを示しています。したがって、解凍された文字列はaac
となります。参照部はaac
に更新されます。
- 次のタプルは(3,4,b)です。参照部内最後尾から3文字前で始まる文字列パターン
aac
が4文字分繰り返され、次の文字がb
であることを示しています。したがって、解凍された文字列はaacaacab
となります。参照部はcaacab
に更新されます。
- 次のタプルは(3,3,a)で、参照部最後尾から3文字前で始まる文字列パターン
cab
が3文字分繰り返され、次の文字がa
であることを示しています。したがって、解凍された文字列はaacaacabcaba
となります。参照部はabcaba
に更新されます。
- 最後のタプルは(1,2,c)です。参照部最後尾から1文字前のパターン
a
が2文字分繰り返され、次の文字がc
であることを示しています。したがって、最終的に解凍された文字列はaacaacabcabaac
となります。
以上がLZ77アルゴリズムによる解凍手順となります。
LZSSについて
実際のDEFLATEではLZ77を改良したLZSSが使用されています。
LZ77の出力ではパターンの一致があろうとなかろうとオフセット、一致する長さ、そしてパターンに一致しなくなる最初の文字(不一致記号) の組が出力されました。
LZSSの出力は以下のように一致するパターンがある場合とない場合で出力が異なります。
・一致するパターンがない場合
(0、不一致記号)
・一致するパターンが存在する場合
(1、一致するパターンのオフセット、一致する長さ)
符号の先頭の太文字はスライディングウィンドウ内のパターンに一致したかどうかを表す1bitのフラグです。
このようにすることで一致するバターンがないときにオフセット、一致長を符号化する必要がないのでさらに圧縮サイズが効率化されます。
まとめ
DEFLATEアルゴリズムの構成要素であるハフマン符号とLZ77(LZSS)アルゴリズムについて解説しました。
次回の記事ではこれらの要素がDEFLATEの中でどのように組み合わさっているのかについて解説します。
この記事が参考になった方はいいねを押してもらえると執筆の励みになります。よろしくお願いします。
参考
Discussion