- ベストアンサー
ハフマン符号プログラムの作成と圧縮率の測定
- ハフマン符号によるファイルの圧縮をC言語で行うプログラムを作成し、圧縮率を測定する。
- 圧縮プログラムでは、入力ファイルを読み込んで各記号の出現頻度をカウントし、ハフマン符号を生成し出現頻度を出力ファイルに書き出す。
- 解凍プログラムでは、圧縮ファイルから各符号の出現頻度を読み込み、ハフマン符号を生成して解凍ファイルに出力する。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
コンパイルが通る所迄は修正しましたが、分岐木の生成が正しく出来ていない感じがします。 この辺を、どの様に考えて作ろうとしているのか補足願えますか? もしかしたら、私が考え方を間違って説明しているかも知れません。 生成されて来た分岐木データが、どうも単純にコードと対になって無い様子なのですが...。 取り合えず、現状のソースを張って置きます。 /* Quenista */ の入ってる行が、いじった所です。 /**************************************************** ハフマン符号 ****************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> /* Quenista */ #define DEBUG #define SIZE 256 /* 二分木 */ typedef struct _node { unsigned int count; /* 頻度 */ int parent; /* 上の要素番号 */ int left; /* 左側を 0 */ int right; /* 右側を 1 */ } CODE; CODE code[2*SIZE+1]; int mozi_count[SIZE]; int search_code; /* Quenista */ int Hcode[SIZE][SIZE]; int hcode_count[SIZE]; /* ? */ int bit_count; FILE *fp1,*fp2; void put_bit(unsigned int bit); void /* Quenista */ main() { int i, total = 0; char input_fname[] = "read.txt"; char output_fname[] = "out2.txt"; int min1, min2, freeNode, root; #ifdef DEBUG printf("a"); /* Quenista Debug */ #endif /* データの初期化 */ for( i = 0; i < 2*SIZE+1; i++ ) code[i].count = 0; for( i = 0; i < SIZE; i++ ) mozi_count[i] = 0; memset((char *)&hcode_count[0],0,sizeof(hcode_count)); /* Quenista */ /* 文字頻度をカウント */ if((fp1 = fopen(input_fname,"r")) == NULL){ fprintf(stderr,"%s: can't opn file\n", input_fname); exit(1); } while((i=fgetc(fp1)) !=EOF){ code[i].count++; mozi_count[i]++; total++; } fclose(fp1); /* Quenista(ファイルは、使わなくなった時点でクローズする方が良い) */ #ifdef DEBUG printf("b"); /* Quenista Debug */ #endif /* ハフマン符号を生成する */ /* ハフマン木をつくる */ code[2*SIZE].count = 0x100; /* 番兵 */ for (freeNode = SIZE; ; freeNode++) { min1 = min2 = 2*SIZE; for (i = 2*SIZE-1; i >= 0; i--) if (code[i].count > 0) { if (code[i].count < code[min1].count) { min2 = min1; min1 = i; } else if (code[i].count < code[min2].count) min2 = i; } if (min2 == 513 - 1) break; code[freeNode].count = code[min1].count + code[min2].count; code[freeNode].left = min1; code[freeNode].right = min2; code[min1].parent = code[min2].parent = freeNode; code[min1].count = code[min2].count = 0; } root = min1; #ifdef DEBUG for(i=0;i<512;i++){ printf("[%3d:親%3d]\n",i,code[i].parent); } /* Quenist Debug */ #endif for(i=0;i<256;i++){ /* コードの回数分のループを行う。 */ search_code=i; /* ←ここで、次に検索するコードを保存して置く */ while(code[search_code].parent<0x100&&code[search_code].parent!=0){ /* ←ここで、戻れなくなるまで探し続ける(親を訪ねて3千里。) */ /* Quenista Offset加算 */ if(code[code[search_code].parent].left==search_code){ Hcode[i][hcode_count[i]]=0; } /* ここで、左に分岐しているのか調べ、左なら0 */ /* Quenista Offset加算 */ else{ Hcode[i][hcode_count[i]]=1; } /* 右なら1を保存 */ search_code=code[search_code].parent; /* 一つ上に戻る */ /* Quenista Offset加算 */ hcode_count[i]++; /* ビット数を、数えて置く */ } } /* (c) */ #ifdef DEBUG printf("c"); /* Quenista Debug */ #endif if((fp2 = fopen(output_fname,"w")) == NULL){ fprintf(stderr,"%s: can't opn file\n", output_fname); exit(1); } #ifdef DEBUG fprintf(fp2,"Header:\n"); /* Quenista Debug */ #endif for(i=0;i<SIZE;i++){ fprintf(fp2,"%d ", mozi_count[i]); } #ifdef DEBUG fprintf(fp2,"\n"); /* Quenista Debug */ #endif if((fp1 = fopen(input_fname,"r")) == NULL){ /* Quenista(再オープンが必要) */ fprintf(stderr,"%s: can't opn file\n", input_fname); /* Quenista */ exit(1); /* Quenista */ } /* Quenista */ #ifdef DEBUG fprintf(fp2,"Data:\n"); /* Quenista Debug */ #ifdef endif bit_count=0; while(!feof(fp1)){ /* Quenista */ search_code=fgetc(fp1); /* Quenista */ for(i=hcode_count[search_code];i>0;i--){ /* Quenista */ put_bit(Hcode[search_code][i-1]); /* Quenista */ bit_count++; } } #ifdef DEBUG printf("d"); /* Quenista Debug */ #endif for(i=0;i<(7-(bit_count&0xF));i++) put_bit(0); /* Quenista */ fclose(fp1); fclose(fp2); } void put_bit(unsigned int bit) { static unsigned int mask = 0x80; static unsigned int byte = 0x00; if(bit != 0){ byte = byte | mask; } mask = mask >> 1; //8bit貯まったか if(mask == 0x00){ if(fputc(byte,fp2) == EOF){ printf("Can't write \n"); exit(1); } mask = 0x80; byte = 0x00; } } /* end of file */
その他の回答 (6)
- quenista
- ベストアンサー率28% (122/425)
先ず、紛らわし表記を使ったのが不味かったのですね。 ごめんなさい。 search_code=code; parent_code=code[code].parent; while(code[code].parent!=0x100){ ここで使っている[code]は、検索すべき値が順次入るコードを入れて欲しかったのです。 つまり、もう少し前後を含め分岐木を辿る部分を書くと、 for(i=0;i<256;i++){ /* コードの回数分のループを行う。 */ parent_code=i; /* ←ここで、次に検索するコードを保存して置く */ while(code[parent_code].parent!=0x100){ /* ←ここで、戻れなくなるまで探し続ける(親を訪ねて3千里。) */ if(code[code[parent_code].parent].left==parent_code){ Hcode[i][hcode_count[i]]=0; } /* ここで、左に分岐しているのか調べ、左なら0 */ else{ Hcode[i][hcode_count[i]]=1; } /* 右なら1を保存 */ parent_code=code[parent_code].parent; /* 一つ上に戻る */ hcode_count[i]++; /* ビット数を、数えて置く */ } } と言う感じだと思います。 少し気になったのは、この辺の動きを理解されているか解らないので、出来るだけ説明の1つのコードの部分に絞って記述した見てたのです。 search_codeについては、検索コードを明示的に書いただけです。 parent_codeいついては、code[].parentの型 つまりint型で定義すれば良いですね。 hcode_countについては、各コードのビット数をカウントして置く場所ですので、コードの数。つまり256個の箱が有れば良い事に成ります。 又、このソースからは、ビット数が256を越える事は無いのでchar型でも良いのですが、数値を入れるのでint型で取る方が良いと思います。 つまり、 int hcode_count[256]; で良いですね。 後、初期化は最初はビット数は0としたいので、 memset((char *)&hcode_count[0],0,sizeof(hcode_count)); として初期化して置いてやれば良いと思います。 絵には描き難いのですが...。 親―子―孫A \ \ 子 孫B |\ 孫 孫C D と言う感じで分岐木が出来ているのを、孫から親に辿って行く作業を行っています。 それを孫から戻る時に孫は子の左右のどちらに居るかを、コードとして拾っています。 又、子から親に戻る時も親のどちら側に居るかを辿って居ます 例えば、孫Cから戻ると子の右側に繋がって居て、その子は親の左側に繋がっている事になり、それをこのコードで表すと、[0],[1]となります。 それを逆から読んだ物がハフマン符号になります。 (つまり、[10]) この戻った回数が、ビット数として保存して置いてます。 ビット数は、出現頻度の高いデータは短く、出現頻度の少ないデータは多くなる事になりますね。 ハフマン符号の分岐木の生成とデータ分布率の部分は、少し考えないと難しいかも知れませんが、良く理解してコードに落として行って下さいね。 もしこの辺が解りにくい場合は、絵に書く等して見ると解りやすいかも知れません。 >よろしかったらデバックしてください。 少し忙しくなって来てまして余り早く回答出来ないかも知れませんが、こちらでも確認して見ます。 それと、このプログラムコードはまだまだ改善出来る個所が多々有りそうですので、全体の動作を良く押えて見て下さいね。
- quenista
- ベストアンサー率28% (122/425)
for(loop=0;loop<(bit_count&0xF);loop++) bit_put(0); ここ、間違ってました。 足りない分を補うので、 for(loop=0;loop<(7-(bit_count&0xF));loop++) bit_put(0); が正しいですね。 検証してないので、他にもバグってるかも知れません。 注意して下さいね。
- quenista
- ベストアンサー率28% (122/425)
全体のソースでは無い様ですし、全てのロジックを確認した訳では無いので、一つ一つの動作は御自分で確認して見て下さい。 /* 各符号から上の要素番号をたどれなくなるまでたどりながら、自分が左側にいたら0を右側にいたら1を並べていくと、その逆順がその記号のハフマン符号となる。*/ /* これを各符号に対して行い,それぞれのハフマンコードを2次元配列Hcodeに格納する。 /* 例えば,記号('A' = 65)のハフマンコードが"011"の場合 Hcode[65][0] = 0; Hcode[65][1] = 1; Hcode[65][2] = 1; */ 先ず、Hcode[][]の配列がこのプログラム中には無いですが、他に有るのですね? parentに値が有る間(0x100以外の時)は、何処かの分岐木の下で有ると言う事です。 このソースの場合、デーブル・チェーン構造になってますので、それを辿って下さいと言う事です。 例えば、あるコード一つで見てみると、code[(コード)].parentの中に上位のコード番号が入っています。 そのコードに戻ると、.left或いは.rightの中にコード番号が入っています。 つまり、 search_code=code; parent_code=code[code].parent; while(code[code].parent!=0x100){ if(code[code[parent_code].parent].left==parent_code){ Hcode[search_code][0]=0; } else{ Hcode[search_code][0]=1; } parent_code=code[parent_code].parent; hcode_count[search_code]++; } と言う様な事を、256文字分行えば良いと思います。 hcode_countは、後で使う為に追加しました。(0初期化) fclose(fp2); ↑put_bit関数の中が解らないのですが、データを書き込んでからクローズした方が良いのでは? /* (d) */ /* もう一度ファイルを読み込み,その記号に対応するハフマンコードをファイルに書いていく。 */ /* (d)の書き込みにはput_bit関数を使って書き込むことができる。 */ /* また注意点として,put_bit関数は8bitたまった時点でファイルに書き込みを行うので,ファイルの記号をすべて処理した時に,最後に8bitたまっていない場合残りのbitに'0'を書き込む必要がある。*/ ファイルのオープンは解って居られると思いますので、他の部分だけ...。 上記のロジックで、Hcodeにハフマンコードが、hcode_countにハフマンコードのビット数が入ってます。 それを、Fileに書き出せば良いのです。 又、書き出したビット数を数えて置くと、空白を埋めるビット数は解ると思います。 bit_count=0; while((i=fgetc(fp1))!=EOF){ for(hcode_count[i]-1;loop>=0;loop++){ bit_put(Hcode[search_code][loop]); bit_count++; } } for(loop=0;loop<(bit_count&0xF);loop++) bit_put(0); てな感じですかね? 最後に、ファイルのクローズを忘れずに...。 一つ一つの動きを良く理解出来ますよう、頑張って下さいね。
補足
search_code=code; parent_code=code[code].parent; while(code[code].parent!=0x100){ if(code[code[parent_code].parent].left==parent_code){ Hcode[search_code][0]=0; } else{ Hcode[search_code][0]=1; } parent_code=code[parent_code].parent; hcode_count[search_code]++; } CODE search_code; int parent_code; と定義すると,parent_code=code[code].parent; のところで error C2107: ポインタでない式に、添字が使われました。 error C2231: '.parent' : 左のオペランドが 'struct' へのポインタです。'->' を使用してください。 とエラーになってしまいました。 int *parent_code; と定義すると,また parent_code=code[code].parent; のところで error C2107: ポインタでない式に、添字が使われました。 error C2231: '.parent' : 左のオペランドが 'struct' へのポインタです。'->' を使用してください。 warning C4047: '=' : 間接参照のレベルが 'int *' と 'int ' で異なっています。 とエラーになってしまいました。 どうすればよいのでしょうか。 また,hcode_countはどのように定義すればよいのでしょうか。とりあえず,いまの段階までのプログラムを送ります。よろしかったらデバックしてください。 /**************************************************** ハフマン符号 ****************************************************/ #include <stdio.h> #include <stdlib.h> #define SIZE 256 /* 二分木 */ typedef struct _node { unsigned int count; /* 頻度 */ int parent; /* 上の要素番号 */ int left; /* 左側を 0 */ int right; /* 右側を 1 */ } CODE; CODE code[2*SIZE+1]; CODE *search_code; int mozi_count[SIZE]; int *parent_code; int Hcode[SIZE][SIZE]; int hcode_count[SIZE]; /* ? */ int bit_count; FILE *fp1,*fp2; void put_bit(unsigned int bit); int main() { int i, total = 0; char input_fname[] = "read.txt"; char output_fname[] = "out2.txt"; int min1, min2, freeNode, root; /* (a) */ /* データの初期化 */ for( i = 0; i < 2*SIZE+1; i++ ) code[i].count = 0; for( i = 0; i < SIZE; i++ ) mozi_count[i] = 0; /* 文字頻度をカウント */ if((fp1 = fopen(input_fname,"r")) == NULL){ fprintf(stderr,"%s: can't opn file\n", input_fname); exit(1); } while((i=fgetc(fp1)) !=EOF){ code[i].count++; mozi_count[i]++; total++; } /* (b) */ /* ハフマン符号を生成する */ /* ハフマン木をつくる */ code[2*SIZE].count = 0x100; /* 番兵 */ for (freeNode = SIZE; ; freeNode++) { min1 = min2 = 2*SIZE; for (i = 2*SIZE-1; i >= 0; i--) if (code[i].count > 0) { if (code[i].count < code[min1].count) { min2 = min1; min1 = i; } else if (code[i].count < code[min2].count) min2 = i; } if (min2 == 513 - 1) break; code[freeNode].count = code[min1].count + code[min2].count; code[freeNode].left = min1; code[freeNode].right = min2; code[min1].parent = code[min2].parent = freeNode; code[min1].count = code[min2].count = 0; } root = min1; search_code=code; parent_code=code[code].parent; while(code[code].parent!=0x100){ if(code[code[parent_code].parent].left==parent_code){ Hcode[search_code][0]=0; } else{ Hcode[search_code][0]=1; } parent_code=code[parent_code].parent; hcode_count[search_code]++: } /* (c) */ if((fp2 = fopen(output_fname,"w")) == NULL){ fprintf(stderr,"%s: can't opn file\n", output_fname); exit(1); } for(i=0;i<SIZE;i++){ fprintf(fp2,"%d ", mozi_count[i]); } bit_count=0; while((i=fgetc(fp1))!=EOF){ for(hcode_count[i]-1;loop>=0;loop++){ put_bit(Hcode[search_code][loop]); bit_count++; } } for(loop=0;loop<(7-(bit_count&0xF));loop++) put_bit(0); fclose(fp1); fclose(fp2); } void put_bit(unsigned int bit) { static unsigned int mask = 0x80; static unsigned int byte = 0x00; if(bit != 0){ byte = byte | mask; } mask = mask >> 1; //8bit貯まったか if(mask == 0x00){ if(fputc(byte,fp2) == EOF){ printf("Can't write \n"); exit(1); } mask = 0x80; byte = 0x00; } } /* end of file */ /******************* 関数put_bit 引数として,0,または1を渡すと,1bitづつファイルに書き込む (ファイルポインタはグローバル変数として用意する) 実際には,8bit貯まった時点でbyte単位で出力する *******************/ void put_bit(unsigned int bit) { static unsigned int mask = 0x80; static unsigned int byte = 0x00; if(bit != 0){ byte = byte | mask; } mask = mask >> 1; //8bit貯まったか if(mask == 0x00){ if(fputc(byte,fpcode) == EOF){ printf("Can't write \n"); exit(1); } mask = 0x80; byte = 0x00; } } /*使用例(100bitを書き込む)(配列array[100]に0,1が格納されているとする)*/ /*for(i = 0;i < 100;i++) */ /* put_bit(array[i]); */ /*for(i = 0;i < 7;i++) put_bitは8bit単位で書き込むので最後の */ /* put_bit(0); bitを書き込むために最後の2行が必要となる。 */ /*(この例の場合は,残った4bitを書き込めばよいので,7でなく4でもできる) */
- quenista
- ベストアンサー率28% (122/425)
>実は(a)からあまりよく理解していなかったようで・・・。 では、順に行きましょう。 先ず、ターゲット(圧縮するファイル)の分布を洗い出す為に、ファイルを一度なめると言う動作を考えます。 色々な考え方などが有りますが、取り敢えず一番単純な1バイト単位で考えてみます。 すると、ファイルから1バイトづつ読みながらカウントすれば良いですよね? つまり256個の領域を取って置いて、加算して行けば良いだけです。 こんどは、この256個のデータをソートして、加算された順に並べます。 そして、この256個のデータにハフマンの分岐木を割り当てて行けば良いのです。(勿論、ビットの少ない方から。) そして、そのヘッダー部分を出力形式に変換しながらファイル出力します。 最後にデータの先頭に戻って、各データをビット毎にファイルに出力して行けば良いのです。(シフト等を使いながら。) 逆に解凍の場合は、先にビットパターンからバイトに変換するテーブルを作成し、 1ビット毎に比較(こちらも、シフトとマスクを使って、比較を行えば良い。)を行ない、元のバイトデータに戻して行きます。 これで、詰まった所を教えて頂けますか?
補足
仕様書の内容をまとめると以下のようになりました。 #include <stdio.h> #include <stdlib.h> #define SIZE 256 /* 二分木 */ typedef struct _node { unsigned int count; /* 頻度 */ int parent; /* 上の要素番号 */ int left; /* 左側を 0 */ int right; /* 右側を 1 */ } CODE; CODE code[2*SIZE+1]; int mozi_count[SIZE]; int main() { int i, total = 0; char input_fname[] = "read.txt"; char output_fname[] = "out.txt"; FILE *fp1,*fp2; int min1, min2, freeNode, root; /* (a) */ /* データの初期化 */ for( i = 0; i < 2*SIZE+1; i++ ) code[i].count = 0; for( i = 0; i < SIZE+1; i++ ) mozi_count[i] = 0; /* 文字頻度をカウント */ if((fp1 = fopen(input_fname,"r")) == NULL){ fprintf(stderr,"%s: can't opn file\n", input_fname); exit(1); } while((i=fgetc(fp1)) !=EOF){ code[i].count++; mozi_count[i]++; total++; } fclose(fp1); /* (b) */ /* ハフマン符号を生成する */ /* ハフマン木をつくる */ code[2*SIZE].count = 0x100; /* 番兵 */ for (freeNode = 256; ; freeNode++) { min1 = min2 = 2*SIZE; for (i = 2*SIZE - 1; i >= 0; i--) if (code[i].count > 0) { if (code[i].count < code[min1].count) { min2 = min1; min1 = i; } else if (code[i].count < code[min2].count) in2 = i; } if (min2 == 2*SIZE) break; code[freeNode].count = code[min1].count + code[min2].count; code[freeNode].left = min1; code[freeNode].right = min2; code[min1].parent = code[min2].parent = freeNode; code[min1].count = code[min2].count = 0; } root = min1; /* 各符号から上の要素番号をたどれなくなるまでたどりながら,自分が左側に */ /* いたら0を,右側にいたら1を並べていくと,その逆順がその記号のハフマン */ /* 符号となる。これを各符号に対して行い,それぞれのハフマンコードを2次 */ /* 元配列Hcodeに格納する。例えば,記号('A' = 65)のハフマンコードが"011"*/ /* の場合 Hcode[65][0] = 0; Hcode[65][1] = 1; Hcode[65][2] = 1; */ /* (c) */ if((fp2 = fopen(output_fname,"w")) == NULL){ fprintf(stderr,"%s: can't opn file\n", output_fname); exit(1); } for(i=0;i<SIZE;i++){ fprintf(fp2,"%d ", mozi_count[i]); } fclose(fp2); /* (d) */ /* もう一度ファイルを読み込み,その記号に対応するハフマンコードをファイ */ /* ルに書いていく。 */ /* (d)の書き込みにはput_bit関数を使って書き込むことができる。また注意点 */ /* として,put_bit関数は,8bitたまった時点でファイルに書き込みを行うの */ /* で,ファイルの記号をすべて処理した時に,最後に8bitたまっていない場合 */ /残りのbitに'0'を書き込む必要がある。*/ } /* end of file */ 日本語で書いてあるところがわかりません。
- quenista
- ベストアンサー率28% (122/425)
>(c)から先はよく分かりません。 先ず、単純にファイルを舐めながら、変数に加算して行けば良いと思います。 それを最後に並べ直せば、出現頻度順に並べる事が出来ますので、それをフォーマットに沿った形で吐いてやれば、良いですね。 後は、ハフマンの分岐木を生成して、出現頻度順に割り当ててを行い、 そのルールでビットへ変換して行けば良いでしょう。 後、辞書圧縮は行わないで良いのですよね?
補足
実は(a)からあまりよく理解していなかったようでうまく説明できません。特に(b)がよく分かっていませんでした。 辞書圧縮は行わなくて良いです。
- quenista
- ベストアンサー率28% (122/425)
流石に、ココでソースを書くのは結構困難ですので、 先ず、何処で詰っているか補足頂けますか? 不明個所についてなら、アドバイスが出来るかも知れません。 又、一言でハフマン圧縮と言っても、種類が有りますが、 動的ハフマンですか? 静的ハフマンですか?
補足
静的ハフマン符号です。 ネットで「ハフマン符号」で検索してサンプルプログラムを得たのですが,インクルードファイルが見たこともないものを使っていたのと,はっきり言って理解できなかったんです。 ちなみに,stdio.h stdilb.h string.h math.h くらいしか使ったことがりません。 サンプルプログラムを見て,圧縮の方は(b)のところまではなんとか理解したつもりですが,(c)から先はよく分かりません。 解凍の方はまだ手をつけていません。たぶん,(c)から先がまた分からないと思います。
お礼
返事が遅れてしまって大変申しわけありませんでした。 おかげさまでなんとか提出期限には間に合いました。なにせこのレポートを提出しないと期末試験を受けられなかったものですから。 本当に長い間お世話になりました。ありがとうございました。