目次
1. ハフマン木とは何ですか?
最適ツリーとしても知られるハフマン ツリーは、加重パス長が最も短いツリーの一種であり、実用的なアプリケーションで広く使用されています。ハフマン木の定義には、パス、パス長、重みなどの概念が含まれます。
2. ハフマンツリーの構築プロセス
1. プロセス分析
1. フォレストからノードの重みが最小の 2 つのツリーを見つけて、左右のサブツリーとして新しいバイナリ ツリーを構築します。すでに選択されているツリーは選択されなくなります。
2. 新しく構築したバイナリ ツリーとフォレスト内のツリーを再度比較し、プロセス 1 を繰り返します。
3. 最後にバイナリツリーにマージします
2. プロセスコードの実装
1 つ目は、次に構築するバイナリ ツリーを格納するバイナリ ツリーを初期化することです。初期化プロセス中に、2n ユニットを動的に割り当て、2n-1 回ループしてユニット内のすべてのユニットを 0 に初期化する必要があります。
2 番目のステップは、作成されたツリーにフォレスト内の木の重みを保存することです。
次に、ハフマン ツリーの作成を開始します。
コードは以下のように表示されます。
void CreateHaffmanTree(HaffmanTree &ht,int n)
{
if (n <= 1) return ;
int m = 2 * n - 1;
ht = (HaffmanTree)malloc(sizeof(htNode)*(m+1));
for (int i = 1; i <= m; i++)//初始化双亲结点和孩子节点的值
{
ht[i].parent = 0;
ht[i].lchild = 0;
ht[i].rchild = 0;
}
for (int i = 1; i <= n; i++)//将权值进行存储
{
int x;
scanf("%d", &x);
ht[i].weight = x;
}
//开始创建哈夫曼树
for (int i = n+1; i <= m; i++)
{
int l=0, r=0;
Select(ht, i-1, l, r);//调用Select函数返回最小的两颗树的权值,并且返回下标
ht[i].weight = ht[l].weight + ht[r].weight;
ht[i].lchild = l;//将返回的下标作为新构造树的的左右孩子
ht[i].rchild = r;
ht[l].parent = i;//将新构造树的结点作为最小的两个树的双亲结点
ht[r].parent = i;
}
visit(ht,m);
}
機能選択
この関数は、2 つの最小のツリーの重みと添字を返します。
コードは以下のように表示されます。
void Select(HaffmanTree ht, int n, int &l, int &r)//此函数用于返回权值最小的两个数,并且返回这两个数的节点作为孩子节点
{
int min1 = 9999999;
int min2 = 999999;
for (int i = 1; i <= n; i++)
{
if (ht[i].weight < min1 && ht[i].parent == 0)//返回最小的权值的节点作为左孩子
{
min1 = ht[i].weight;
l = i;
}
}
for (int i = 1; i <= n; i++)
{
if (ht[i].weight < min2 && ht[i].parent == 0)//返回第二小的权值的节点作为右孩子
{
int t = i;
if (l!=t)//在这里卡了好久,没想到用下标来判断一组数据中最小的两个数
{
min2 = ht[i].weight;
r = i;
}
}
}
}
3. ハフマン符号化の実装
ハフマン ツリーを構築した後、ハフマン コーディングを見つける主な考え方は、リーフ ノードを開始点として取り、ルート ノードまで上向きにたどることです。バックトラッキングとは、左の子が 0、子が 1 であると規定することです。
各ハフマンコードは可変長コードであるため、各文字列コードの先頭アドレスを格納するためにポインタが使用されます。
コードは以下のように表示されます。
void CreatHaffmanCode(HaffmanTree ht, HaffmanCode& hc, int n)
{
hc = (HaffmanCode)malloc(sizeof(char*) * (n + 1));//分配存储n个编码的空间,其实用不到n个空间,但是为了想用的时候有,直接开辟n个空间就行
char *cd = (char*)malloc(sizeof(char) * n); //分配临时存放字符编码的动态数组空间
cd[n - 1] = '\0'; //编码结束符
for (int i = 1; i <= n; i++)
{
int start = n - 1;//回溯的过程是由下往上的,所以存储的时候直接由后往前存
int c = i;//标记点,用来判断左右孩子
int f = ht[i].parent;//f直接指向c的双亲结点,
while (f!=0)
{
start--;
if (ht[f].lchild == c)//找到第一个节点的双亲结点后判断左右孩子
{
cd[start] = '0';
}
else
{
cd[start] = '1';
}
c = f;
f = ht[f].parent;//递归的思想,继续往上回溯
}
hc[i] = (char*)malloc((n - start) * sizeof(char));//为第i个字符分配空间,即为了保存第i个字符的编码
strcpy(hc[i], &cd[start]);//将求到的编码复制到ht空间去
}
free(cd);//释放临时空间
visitCode(ht, hc, n);//开始打印
}
完全なコード
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <malloc.h>
#include <string.h>
typedef struct
{
int weight;
int parent;
int lchild;
int rchild;
}htNode, * HaffmanTree;
typedef char** HaffmanCode;
void Select(HaffmanTree ht, int n, int &l, int &r)//此函数用于返回权值最小的两个数,并且返回这两个数的节点作为孩子节点
{
int min1 = 9999999;
int min2 = 999999;
for (int i = 1; i <= n; i++)
{
if (ht[i].weight < min1 && ht[i].parent == 0)//返回最小的权值的节点作为左孩子
{
min1 = ht[i].weight;
l = i;
}
}
for (int i = 1; i <= n; i++)
{
if (ht[i].weight < min2 && ht[i].parent == 0)//返回第二小的权值的节点作为右孩子
{
int t = i;
if (l!=t)//在这里卡了好久,没想到用下标来判断一组数据中最小的两个数
{
min2 = ht[i].weight;
r = i;
}
}
}
}
void visit(HaffmanTree ht, int m)
{
for (int i = 1; i <= m; i++)
{
printf("%-12d%-12d%-12d%-12d\n", ht[i].weight, ht[i].parent, ht[i].lchild, ht[i].rchild);
}
}
void CreateHaffmanTree(HaffmanTree &ht,int n)
{
if (n <= 1) return ;
int m = 2 * n - 1;
ht = (HaffmanTree)malloc(sizeof(htNode)*(m+1));
for (int i = 1; i <= m; i++)//初始化双亲结点和孩子节点的值
{
ht[i].parent = 0;
ht[i].lchild = 0;
ht[i].rchild = 0;
}
for (int i = 1; i <= n; i++)//将权值进行存储
{
int x;
scanf("%d", &x);
ht[i].weight = x;
}
//开始创建哈夫曼树
for (int i = n+1; i <= m; i++)
{
int l=0, r=0;
Select(ht, i-1, l, r);
ht[i].weight = ht[l].weight + ht[r].weight;
ht[i].lchild = l;
ht[i].rchild = r;
ht[l].parent = i;
ht[r].parent = i;
}
printf("初始权值 双亲结点 左孩子 右孩子\n");
visit(ht,m);
}
void visitCode(HaffmanTree ht, char**hc,int n)
{
printf("--------------------哈夫曼树的编码-------------------\n");
printf("初始权值 哈夫曼编码\n");
for (int i = 1; i <= n; i++)
{
printf("%-12d%-12s%\n", ht[i].weight, hc[i]);
}
}
void CreatHaffmanCode(HaffmanTree ht, HaffmanCode& hc, int n)
{
hc = (HaffmanCode)malloc(sizeof(char*) * (n + 1));//分配存储n个编码的空间,其实用不到n个空间,但是为了想用的时候有,直接开辟n个空间就行
char *cd = (char*)malloc(sizeof(char) * n); //分配临时存放字符编码的动态数组空间
cd[n - 1] = '\0'; //编码结束符
for (int i = 1; i <= n; i++)
{
int start = n - 1;//回溯的过程是由下往上的,所以存储的时候直接由后往前存
int c = i;//标记点,用来判断左右孩子
int f = ht[i].parent;//f直接指向c的双亲结点,
while (f!=0)
{
start--;
if (ht[f].lchild == c)//找到第一个节点的双亲结点后判断左右孩子
{
cd[start] = '0';
}
else
{
cd[start] = '1';
}
c = f;
f = ht[f].parent;//递归的思想,继续往上回溯
}
hc[i] = (char*)malloc((n - start) * sizeof(char));//为第i个字符分配空间,即为了保存第i个字符的编码
strcpy(hc[i], &cd[start]);//将求到的编码复制到ht空间去
}
free(cd);//释放临时空间
visitCode(ht, hc, n);//开始打印
}
int main()
{
HaffmanTree ht;
HaffmanCode hc;
int n ;
printf("请输入n个数的权值: ");
scanf("%d", &n);
printf("--------------------哈夫曼树的构造-------------------\n");
printf("请输入每个数的权值:");
int l = 0, r = 0;
CreateHaffmanTree(ht, n);
printf("\n");
CreatHaffmanCode(ht, hc, n);
return 0;
}
要約する
以上がハフマン木の構築と符号化の実装ですが、その過程でも、大きさを比較する際の添字の使い方が分からないなど、多くの問題点が見つかり、勉強になることも多かったです。