コラム
2025/03/31
第103回 プログラミングについて『ファイルを圧縮してみよう! その2』

ファイルを圧縮といえば、UNIXでは pack や compress、DOSではフリーソフトの lha 、zip などがあり、多分このどれかを一度は使った経験があると思います。これらのソフトは圧縮したファイルを再び伸長すると完全に元のファイルが再現できる機能を持っています。
これに対し、画像を圧縮するソフトは必ずしもそうではありません。というのも画像は少しくらいいい加減なデータであっても人間の目というものはそれほど高機能ではないので(決して低機能という意味ではありません)、品質を少し下げてでも圧縮率を上げる方を優先しているからなのです。
今回は、圧縮したファイルを完全に伸長できるアルゴリズムについて考えてみます。
UNIXには pack と compress という2つの圧縮プログラムがあります。この2つは圧縮に使用しているアルゴリズムが全く異なっています。pack はハフマン法、compressはLZ法という聞き慣れないアルゴリズムを使用しています。lha はLZ法とハフマン法の両方を使っており(多分LZ法とHuffman法のArchiverということでLZAと命名したのだと思います。)、zip も同様だと思います。
以前こんなことがありました。まだ私が前の会社で働いていた頃のことです。私のいた工場から別の製造工場にデータを転送するということをやったことがあります。今から15年ぐらい前のことだったと思います。
その頃はあまり難しいことも知らなかったので、単純にコンピュータとコンピュータをRS-232Cからモデムで接続して一般の電話回線でデータを転送しようということになりました。物理的に接続した後、転送側も受信側も copy コマンドを使うことで単純に実現しようとしたのですが、どうしてもうまく行きません。ある程度は転送できるのですがデータが完全に転送できず、使い物にならないのです。色々と調べてみると、9600ボーという転送速度が原因だったようです。9600ボーというと通常ホストコンピュータと端末間の転送速度で高速(今となってはそうでもありませんが)にデータを交換するときに使用するボーレートなのです。
そこで順次ボーレートを下げていき、600ボーではまず間違いなく転送でき、1200ボーでは若干の不安が残る程度ということが分かりました。必ずしも完璧ではないので転送ソフトと受信ソフトを作成し、受け取ったデータが正しいのかを送信側にエコーバックするという方法で常に正確に転送できるようになりました。
ところが、600ボー程度では転送にかかる時間が数十分単位となってしまいいらいらして終了するのを待たなければなりません。そこで転送するデータを少なくしようと
データの圧縮を行なったのです。
そのときに私が圧縮に使ったアルゴリズムは特に有名なものではなく、自分で考えたものでした。転送するデータの内容が、
abcde 1 fghijkl 2 mnopq 3 rstuvwxyz
abcde 1 fghijkl 2 mnopq 4 rstuvwxyz
abcde 2 fghijkl 3 mnopq 4 rstuvwxyz
abcde 2 fghijkl 3 mnopq 5 rstuvwxyz
abcde 3 fghijkl 4 mnopq 4 rstuvwxyz
といった感じのもので、それぞれの行の一部分だけに差異があるものでした。そこで、最初の行はそのままとして、2行目以降は前の行と同じ部分の情報と違う部分の情報
にすることにしました。だいたい次のような感じです。
d35,abcde 1 fghijkl 2 mnopq 3 rstuvwxyz
s24,d1,4s10
s6,d1,2s9,d1,3s18
s24,d1,5s18
s6,d1,3s9,d1,4s6,d1,4s18
この形式は d の後に前の行との違う桁数(コンマで桁数が終了)、コンマの後に文字列が続きます。s の後には前の行と同じ部分の桁数(コンマで桁数が終了)という単純な
形式です。この約束事で元のデータに戻してみて下さい。
このようなデータの場合には有効ではありますが、汎用的には役には立たないでしょう。
そこで一般的なアルゴリズムの中からハフマン法を考えてみましょう。ハフマン法とはその名の如くハフマンおじさんが40年程昔に考えたアルゴリズムで、文字の出現頻
度を利用した圧縮アルゴリズムです。
いまファイル内のデータが、
ABCDEABABBCBCb
だったとします。このデータの場合、それぞれの文字の数は、
B 5個
A 3個
C 3個
D 1個
E 1個
となっていますので、出現回数の多い文字を少ないビットで、少ない文字を多いビットで表せば圧縮される可能性があります。
(ここからしばらくは私が勝手に考えてうまくいかなかった方法です。)
そこで、各文字を次のようなビットで表現しようと考えました。
B 1
A 01
C 001
D 0001
E 00001
圧縮したファイルには0の個数に対応した文字のテーブルを書き込んでおき、伸長するときにはビットを順番に読んでいき、0の個数でテーブルのインデックス番号を決定することができるはずです。このデータの場合、ビット列の最長のものでも5ビットしか使わないので確実に圧縮できるはずです。
ところがこのアルゴリズムを使用したプログラムを作って実際に試してみると、予想とは全然違ってしまいました。圧縮されるファイルもあれば、逆に大きくなってしまうものもあるのです。
原因は次のようなことでした。
当然のことですが、文字は全部で256種類あり、頻度の一番大きい文字が1ビットと次のものが2ビットと少ないビット数で表現できるのですが、頻度が9番目以降は9ビット以上使用し、最も頻度の少ない文字は256ビット使用することになります。ですので文字の種類が多くあまり頻度の差がないデータでは逆に大きくなってしまったという訳です。
多分ハフマンおじさんも同じことをやって頭を悩ましたはずです。ここからが私とこのおじさんの違いなのでしょうね。ハフマンおじさんは上の様に単純に表現するのでは
なく別の方法を考えたのです。
ここで2分木のツリーを思い出して下さい。(ここは私の憶測です。)
A
┌─┴─┐
B C
┌┴┐ ┌┴┐
D E F G
理想的な2分木とはこの図のように左右のバランスがとれているもので、このときに検索の効率が最大になります。256種類のデータを検索するときには最大でも8回で済
んでしまいます。次にバランスの悪い2分木を見てみると、
A
┌─┴─┐
B C
┌┴┐
F G
┌┴┐
D E
Bは2回の比較で発見できますが、DやEは4回の比較が必要になります。ところがこれを逆に考えるとBを発見するための比較の回数がDやEよりも少ないということは、
このようなバランスの悪い2分木を利用すると頻度の大きい文字をツリーの浅いところに、そうでないものを深いところに配置するとファイルを圧縮できる可能性があるとい
うことです。多分ハフマンおじさんはこう考えたのだと思います。
再び
ABCDEABABBCBC
5 B
3 A
3 C
1 D
1 E
の文字列の頻度に戻りましょう。ハフマンおじさんはこのようなバランスの悪い木を作るのに、次のように考えました。
もっとも頻度の低いもの2つを選んで木を作り、その2つの頻度の合計を木の頻度とし、どんどん木を成長させていく。
その考えどおりに木を作ってみると、最初は頻度1どうしの D と E で1つの木が出来上がります。
5 B
3 A
3 C
┌ 1 D
2┤
└ 1 E
次に今作った木の頻度が 2 で最少、次に小さいのが A か C ですので、ここでは C と木を作ります。
5 B
3 A
┌3 C
5┤
│ ┌ 1 D
└2┤
└ 1 E
これを繰返して、
┌5 B
8┤
└3 A
┌3 C
5┤
│ ┌ 1 D
└2┤
└ 1 E
最後に
┌B
┌┤
│└A
┤
│┌C
└┤
│┌D
└┤
└E
という木が出来上がります。この木の根から出発して B に行くには上に2回上がればいいのですから、上に行くときには1とすると、11となります。A に行くには10、D
は001になります。
┌B
1│
↑│
1┌┤
↑│└A
スタート┤
↓│┌C
0└┤
│┌D
└┤
└E
この木をハフマン木というのだそうです。
では次回からは実際にハフマン法で圧縮、伸長を行なうプログラムを作ってみましょう。
これに対し、画像を圧縮するソフトは必ずしもそうではありません。というのも画像は少しくらいいい加減なデータであっても人間の目というものはそれほど高機能ではないので(決して低機能という意味ではありません)、品質を少し下げてでも圧縮率を上げる方を優先しているからなのです。
今回は、圧縮したファイルを完全に伸長できるアルゴリズムについて考えてみます。
UNIXには pack と compress という2つの圧縮プログラムがあります。この2つは圧縮に使用しているアルゴリズムが全く異なっています。pack はハフマン法、compressはLZ法という聞き慣れないアルゴリズムを使用しています。lha はLZ法とハフマン法の両方を使っており(多分LZ法とHuffman法のArchiverということでLZAと命名したのだと思います。)、zip も同様だと思います。
以前こんなことがありました。まだ私が前の会社で働いていた頃のことです。私のいた工場から別の製造工場にデータを転送するということをやったことがあります。今から15年ぐらい前のことだったと思います。
その頃はあまり難しいことも知らなかったので、単純にコンピュータとコンピュータをRS-232Cからモデムで接続して一般の電話回線でデータを転送しようということになりました。物理的に接続した後、転送側も受信側も copy コマンドを使うことで単純に実現しようとしたのですが、どうしてもうまく行きません。ある程度は転送できるのですがデータが完全に転送できず、使い物にならないのです。色々と調べてみると、9600ボーという転送速度が原因だったようです。9600ボーというと通常ホストコンピュータと端末間の転送速度で高速(今となってはそうでもありませんが)にデータを交換するときに使用するボーレートなのです。
そこで順次ボーレートを下げていき、600ボーではまず間違いなく転送でき、1200ボーでは若干の不安が残る程度ということが分かりました。必ずしも完璧ではないので転送ソフトと受信ソフトを作成し、受け取ったデータが正しいのかを送信側にエコーバックするという方法で常に正確に転送できるようになりました。
ところが、600ボー程度では転送にかかる時間が数十分単位となってしまいいらいらして終了するのを待たなければなりません。そこで転送するデータを少なくしようと
データの圧縮を行なったのです。
そのときに私が圧縮に使ったアルゴリズムは特に有名なものではなく、自分で考えたものでした。転送するデータの内容が、
abcde 1 fghijkl 2 mnopq 3 rstuvwxyz
abcde 1 fghijkl 2 mnopq 4 rstuvwxyz
abcde 2 fghijkl 3 mnopq 4 rstuvwxyz
abcde 2 fghijkl 3 mnopq 5 rstuvwxyz
abcde 3 fghijkl 4 mnopq 4 rstuvwxyz
といった感じのもので、それぞれの行の一部分だけに差異があるものでした。そこで、最初の行はそのままとして、2行目以降は前の行と同じ部分の情報と違う部分の情報
にすることにしました。だいたい次のような感じです。
d35,abcde 1 fghijkl 2 mnopq 3 rstuvwxyz
s24,d1,4s10
s6,d1,2s9,d1,3s18
s24,d1,5s18
s6,d1,3s9,d1,4s6,d1,4s18
この形式は d の後に前の行との違う桁数(コンマで桁数が終了)、コンマの後に文字列が続きます。s の後には前の行と同じ部分の桁数(コンマで桁数が終了)という単純な
形式です。この約束事で元のデータに戻してみて下さい。
このようなデータの場合には有効ではありますが、汎用的には役には立たないでしょう。
そこで一般的なアルゴリズムの中からハフマン法を考えてみましょう。ハフマン法とはその名の如くハフマンおじさんが40年程昔に考えたアルゴリズムで、文字の出現頻
度を利用した圧縮アルゴリズムです。
いまファイル内のデータが、
ABCDEABABBCBCb
だったとします。このデータの場合、それぞれの文字の数は、
B 5個
A 3個
C 3個
D 1個
E 1個
となっていますので、出現回数の多い文字を少ないビットで、少ない文字を多いビットで表せば圧縮される可能性があります。
(ここからしばらくは私が勝手に考えてうまくいかなかった方法です。)
そこで、各文字を次のようなビットで表現しようと考えました。
B 1
A 01
C 001
D 0001
E 00001
圧縮したファイルには0の個数に対応した文字のテーブルを書き込んでおき、伸長するときにはビットを順番に読んでいき、0の個数でテーブルのインデックス番号を決定することができるはずです。このデータの場合、ビット列の最長のものでも5ビットしか使わないので確実に圧縮できるはずです。
ところがこのアルゴリズムを使用したプログラムを作って実際に試してみると、予想とは全然違ってしまいました。圧縮されるファイルもあれば、逆に大きくなってしまうものもあるのです。
原因は次のようなことでした。
当然のことですが、文字は全部で256種類あり、頻度の一番大きい文字が1ビットと次のものが2ビットと少ないビット数で表現できるのですが、頻度が9番目以降は9ビット以上使用し、最も頻度の少ない文字は256ビット使用することになります。ですので文字の種類が多くあまり頻度の差がないデータでは逆に大きくなってしまったという訳です。
多分ハフマンおじさんも同じことをやって頭を悩ましたはずです。ここからが私とこのおじさんの違いなのでしょうね。ハフマンおじさんは上の様に単純に表現するのでは
なく別の方法を考えたのです。
ここで2分木のツリーを思い出して下さい。(ここは私の憶測です。)
A
┌─┴─┐
B C
┌┴┐ ┌┴┐
D E F G
理想的な2分木とはこの図のように左右のバランスがとれているもので、このときに検索の効率が最大になります。256種類のデータを検索するときには最大でも8回で済
んでしまいます。次にバランスの悪い2分木を見てみると、
A
┌─┴─┐
B C
┌┴┐
F G
┌┴┐
D E
Bは2回の比較で発見できますが、DやEは4回の比較が必要になります。ところがこれを逆に考えるとBを発見するための比較の回数がDやEよりも少ないということは、
このようなバランスの悪い2分木を利用すると頻度の大きい文字をツリーの浅いところに、そうでないものを深いところに配置するとファイルを圧縮できる可能性があるとい
うことです。多分ハフマンおじさんはこう考えたのだと思います。
再び
ABCDEABABBCBC
5 B
3 A
3 C
1 D
1 E
の文字列の頻度に戻りましょう。ハフマンおじさんはこのようなバランスの悪い木を作るのに、次のように考えました。
もっとも頻度の低いもの2つを選んで木を作り、その2つの頻度の合計を木の頻度とし、どんどん木を成長させていく。
その考えどおりに木を作ってみると、最初は頻度1どうしの D と E で1つの木が出来上がります。
5 B
3 A
3 C
┌ 1 D
2┤
└ 1 E
次に今作った木の頻度が 2 で最少、次に小さいのが A か C ですので、ここでは C と木を作ります。
5 B
3 A
┌3 C
5┤
│ ┌ 1 D
└2┤
└ 1 E
これを繰返して、
┌5 B
8┤
└3 A
┌3 C
5┤
│ ┌ 1 D
└2┤
└ 1 E
最後に
┌B
┌┤
│└A
┤
│┌C
└┤
│┌D
└┤
└E
という木が出来上がります。この木の根から出発して B に行くには上に2回上がればいいのですから、上に行くときには1とすると、11となります。A に行くには10、D
は001になります。
┌B
1│
↑│
1┌┤
↑│└A
スタート┤
↓│┌C
0└┤
│┌D
└┤
└E
この木をハフマン木というのだそうです。
では次回からは実際にハフマン法で圧縮、伸長を行なうプログラムを作ってみましょう。