ハフマン (Huffman) コーディングの C 言語実装
この記事では、C 言語でのハフマンコーディングの原理、例、C 言語シミュレーション結果、コードを説明します。
1. ハフマン符号化の原理と例
ハフマン符号化は情報源符号化の一種であり、その符号化の目的は、チャネル容量を最も高い符号化効率で利用することである。
たとえば、メッセージが 5 つの文字シーケンスで構成されており、各文字の確率が a、b、c、d、e として設定されているとします。発生確率は0.12、0.40、0.15、0.08、0.25です。2 つのエンコーディング (マッピング) を以下に示します。
キャラクター | 記号的確率 | 符号化方式1 | 符号化方式2 |
---|---|---|---|
ある | 0.12 | 000 | 000 |
b | 0.40 | 001 | 11 |
c | 0.15 | 010 | 01 |
d | 0.08 | 011 | 001 |
e | 0.25 | 100 | 10 |
エンコード方法 1 の 3 桁の 2 進数の文字列は、別の 3 桁の 2 進数の文字列のプレフィックスではないため、プレフィックスがあることは間違いありません。デコード時には、毎回 3 つの 2 進数の文字列が取得され、3 ビットごとに 1 文字としてデコードされます。
エンコードモード 2 には実際にはプレフィックスがあります。ただし、エンコードのビット長が異なるため、プレフィックスが付いているかどうかを確認するのは困難です。そのコードを表すためにバイナリ ツリーを使用することもできます (プレフィックス コードはバイナリ ツリーで表すことができます)。
したがって、プレフィックス コードはバイナリ ツリー内のパスとして見ることができます。各ノードの左の枝には 0 が、右の枝には 1 が付加されます。葉ノードのラベルとして文字を使用します。根ノードから葉ノードまでの経路上に現れる0または1の並びが、葉ノードの性質に対応するコードとなる。
エンコード モード 1 の場合、すべての文字のコード長は 3 で、平均コード長は 3 です。ただし、符号化モード 2 の平均符号化長は 2.2 です。明らかに、エンコード モード 2 の方が効率的です。
ハフマンアルゴリズムを使用することで、最適なプレフィックスコードを取得できます。
まず、指定された文字セットから出現確率が最も低い 2 つの文字を選択します (前の例では文字 a と文字 d とします)。親ノードを構築します。シンボルは x に設定され、その対応する確率は a と d のシンボル確率の合計であり、その子ノードはそれぞれ a と d です。次に、残りのノードと新規ノードから構成される文字セットと確率セットについても同様にプレフィックス符号化二分木を再帰的に取得します。最適なプレフィックス コードは、バイナリ ツリーをトラバースすることで取得できます。
その構築順序は次のとおりです。
上記の方法を通じて、最適なエンコード方法を取得できます。
キャラクター | 記号的確率 | ハフマン符号化 |
---|---|---|
ある | 0.12 | 1111 |
b | 0.40 | 0 |
c | 0.15 | 110 |
d | 0.08 | 1110 |
e | 0.25 | 10 |
計算後、平均コード長は 2.15 であることがわかります。
2.ハフマンコーディングのC言語実装
シンボルはA~Pの計16個で、その出現確率は以下の通りです。
シンボル | 確率 |
---|---|
あ | 0.06 |
B | 0.12 |
C | 0.15 |
D | 0.05 |
E | 0.06 |
F | 0.02 |
G | 0.07 |
H | 0.03 |
私 | 0.13 |
J | 0.09 |
K | 0.07 |
L | 0.06 |
M | 0.02 |
N | 0.02 |
○ | 0.01 |
P | 0.04 |
1. 初期化
まず初期情報を入力してノードツリーを設定しますが、ポインタ変数ではなくインデックスをアドレスとして使用します。
struct Huffman
{
double weight;
int lchild;
int rchild;
int parent;
}; //Huffman tree结构体定义
この構造は二分木のノードを表しており、設定するノードの数は H として直接定義される node_num になります。
まずバイナリツリーを初期化します。バイナリ ツリーのサイズ (ノード数) は、シンボル数の 2 倍から 1 を引いた値にする必要があります。変更の便宜上、ノードの数、シンボルの数、確率空間についてマクロ定義が行われます。
#define symbol_num 16
#define node_num 2 * (symbol_num) - 1
double symbol_P[symbol_num] = {
0.06, 0.12, 0.15, 0.05, 0.06, 0.02, \
0.07, 0.03, 0.13, 0.09, 0.07, 0.06, 0.02, 0.02, 0.01, 0.04};
初期化前のsymbol_node (16)ノードの重みは、対応する確率です。後続のソートの便宜のために、残りのノードの重みを1に設定します。残りの子ノードについては、データ構造を参照して、親ノードの番号が -1 に設定されます。その後のシンボルのシリアル番号の抽出のために、並べ替えられたシリアル番号を格納するリスト配列を構築します。元の確率空間の破壊を避けるために、新しい初期確率空間を変更用に設定できます。
for (int i = 0; i < node_num; i++)
{
if (i < symbol_num)
symbol_Ptemp[i] = symbol_P[i];
else
symbol_Ptemp[i] = 1;
}
for (int i = 0; i < node_num; i++)
{
list[i] = i;
}
for (int i = 0; i < node_num; i++)
{
H[i].parent = -1;
H[i].lchild = -1;
H[i].rchild = -1;
if (i < symbol_num)
{
H[i].weight = symbol_P[i];
code_len[i] = 0; //为输出方便而设置,后续会解释
}
else
H[i].weight = 0;
}
2. バブルソート
毎回並べ替える必要があり、最小の 2 つの重みが取り出され、戻り値は最小値の数値になります。また、ソートされた順序を保存する必要があり、リスト配列を設定した理由はこの時点で反映されます。ここでの仕分けは、小さいものから大きいものへのバブルソート方式なので、ここでは詳しく説明しません。
int i, j, min0, min1;
double temp;
int temp1;
for (i = 0; i < node_num - 1; i++)
{
for (j = 0; j < node_num - 1 - i; j++)
{
if (symbol_Ptemp[j] > symbol_Ptemp[j + 1])
{
temp = symbol_Ptemp[j];
symbol_Ptemp[j] = symbol_Ptemp[j + 1];
symbol_Ptemp[j + 1] = temp;
temp1 = list[j];
list[j] = list[j + 1];
list[j + 1] = temp1;
}
}
}
min0 = list[0];
min1 = list[1];
3. ノードの構築
ここでのロジックは非常に単純で、最小の 2 つのノードのシリアル番号を取得した場合は、その親ノードを構築するだけです。親ノード構築のステップが完了するたびに、この親ノードの 2 つの子ノードは比較に参加できなくなることを考慮して、抽出後の 2 つの子ノードの重みに 1 を割り当てることをお勧めします。昇順バブリングに影響します。
H[i].weight = H[min0].weight + H[min1].weight;
symbol_Ptemp[i] = H[i].weight;
H[i].lchild = min0;
H[i].rchild = min1;
H[min0].parent = i;
H[min1].parent = i;
symbol_Ptemp[0] = 1.0;
symbol_Ptemp[1] = 1.0;
Bubble();
上記の関数は、最初の親ノードからルート ノードまで再帰的に実行することで完了できます。
3. シミュレーション結果
この実験のコーディング方法は木の形状に関係するため、一意ではないことに注意してください。ただし、結果として得られるコーディング効率は同じで最小限でなければなりません。
4. シミュレーションコード
出力関数はより複雑で、シミュレーションにはあまり役に立たないため、ここでは説明しません。
// Huffman Coding
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define symbol_num 16
#define node_num 2 * (symbol_num) - 1
double symbol_P[symbol_num] = {
0.06, 0.12, 0.15, 0.05, 0.06, 0.02, \
0.07, 0.03, 0.13, 0.09, 0.07, 0.06, 0.02, 0.02, 0.01, 0.04}; //概率空间
double symbol_Ptemp[node_num];//便于比较大小,对于新节点叠加在后
int list[node_num];
int min1, min0;
char code[symbol_num];
int code_len[symbol_num];
struct Huffman
{
double weight;
int lchild;
int rchild;
int parent;
}; //Huffman tree结构体定义
void Initsymbol_P(); //概率空间初始化
void InitHT(Huffman H[node_num]); //节点初始化
void Bubble(); //冒泡排序
void Nodeproduce(); //哈夫曼树构建
void OutputTree();
Huffman H[node_num];
int main()
{
Initsymbol_P();
Bubble();
InitHT(H);
Nodeproduce();
OutputTree();
return 0;
}
void Initsymbol_P()
{
for (int i = 0; i < node_num; i++)
{
if (i < symbol_num)
symbol_Ptemp[i] = symbol_P[i];
else
symbol_Ptemp[i] = 1;
}
for (int i = 0; i < node_num; i++)
{
list[i] = i;
}
}
void InitHT(Huffman H[node_num]) //初始化哈夫曼树
{
for (int i = 0; i < node_num; i++)
{
H[i].parent = -1;
H[i].lchild = -1;
H[i].rchild = -1;
if (i < symbol_num)
{
H[i].weight = symbol_P[i];
code_len[i] = 0;
}
else
H[i].weight = 0;
}
}
void Bubble()
{
int i, j;
double temp;
int temp1;
for (i = 0; i < node_num - 1; i++)
{
for (j = 0; j < node_num - 1 - i; j++)
{
if (symbol_Ptemp[j] > symbol_Ptemp[j + 1])
{
temp = symbol_Ptemp[j];
symbol_Ptemp[j] = symbol_Ptemp[j + 1];
symbol_Ptemp[j + 1] = temp;
temp1 = list[j];
list[j] = list[j + 1];
list[j + 1] = temp1;
}
}
}
min0 = list[0];
min1 = list[1];
}
void Nodeproduce()
{
for (int i = symbol_num; i < node_num; i++)
{
H[i].weight = H[min0].weight + H[min1].weight;
symbol_Ptemp[i] = H[i].weight;
H[i].lchild = min0;
H[i].rchild = min1;
H[min0].parent = i;
H[min1].parent = i;
symbol_Ptemp[0] = 1.0;
symbol_Ptemp[1] = 1.0;
Bubble();
}
}
void OutputTree()
{
for (int i = 0; i < node_num; i++)
{
printf("%d, %f, %d, %d, %d\n",i, H[i].weight, H[i].lchild, H[i].rchild, H[i].parent);
}
for (int i = 0; i < symbol_num; i++)
{
int temp_i = i;
int temp_iup = H[temp_i].parent;
while (temp_iup != -1)
{
code_len[i]++;
temp_i = temp_iup;
temp_iup = H[temp_i].parent;
}
code[code_len[i]] = '\0';
temp_i = i;
temp_iup = H[temp_i].parent;
int j = 1;
while (temp_iup != -1)
{
if (H[temp_iup].lchild == temp_i)
{
code[code_len[i] - j] = 48;
}
if (H[temp_iup].rchild == temp_i)
{
code[code_len[i] - j] = 49;
}
temp_i = temp_iup;
temp_iup = H[temp_i].parent;
j++;
}
printf("'%c': Pro = %f Huffman Code length: %d ", i + 65, H[i].weight, code_len[i]);
printf("Huffman Code -> %s \n", code);
}
}
5. 参考文献
- ハフマン (ハフマン) ツリーとハフマンコーディング
- Tan Haoqiang: C プログラミング (第 4 版)
- Zhang Yan、Li Xiukun、Liu Xianmin: データ構造とアルゴリズム (第 5 版)