实验六 Huffman树及Huffman编码的算法实现

  1. 实验目的

熟悉掌握Huffman树的构造方法及Huffman编码的应用,了解Huffman树在通信、编码领域的应用过程。

  1. 实验内容

(1)输入一段100—200字的英文短文,存入一文件a中。

(2)写函数统计短文出现的字母个数n及每个字母的出现次数

(3)写函数以字母出现次数作权值,建Haffman树(n个叶子),给出每个字母的Haffman编码。

(4)用每个字母编码对原短文进行编码,码文存入文件b中。

(5)用Haffman树对文件b中码文进行译码,结果存入文件c中,比较a,c是否一致,以检验编码、译码的正确性。

  1. 问题描述

    (说明你选做的题目及要求

输入一段100—200字的英文短文,存入一文件a中。写函数统计短文出现的字母个数n及每个字母的出现次数写函数以字母出现次数作权值,建Haffman树(n个叶子),给出每个字母的Haffman编码。用每个字母编码对原短文进行编码,码文存入文件b中。用Haffman树对文件b中码文进行译码,结果存入文件c中,比较a,c是否一致,以检验编码、译码的正确性。

  1. 数据结构定义

    (说明你算法中用到的数据结构、数据类型的定义

定义哈夫曼树对于每个节点而言,既然需要双亲的信息,又需要知道孩子结点的信息。,每一个结点用结构体定义,里面包含此结点的权值weight,父结点parent,左孩子lchild,右孩子rchild。

Typedef struct{

Unsigned int weight;

Unsigned int parent ,lchild,rchild;

};HTNode, *Huffmantree;

Typedef char  * *HuffmanCode;
  1. 算法思想及算法设计

    (先文字说明算法的思想,然后给出类C语言算法

由m个结点创建哈夫曼树。创建的过程实际上是把n个叶子结点的parent赋上值,其余m-n个结点的lchild、rchild和parent(根结点除外,根结点parent为0)赋上值。假设现在正处理第i个节点(i从n+1开始),那么需要从前i - 1个节点中找parent为0(parent为0说明此结点为一棵树的根,它可以作为其它结点的孩子结点)且权值最小的两个结点。这两个结点分别作为第i个结点的左右孩子,第i结点则为这两个结点的双亲。当i的值为m之后,整个树就建立起来了,而且第m个结点为此哈夫曼树的根结点。

void Creat_HT(Node * HT, int n, int m) {

int i; s1;s2;

for (i = n + 1; i <= m; i++) {

Select(HT, i - 1, &s1, &s2);

HT[i].lchild = s1;  HT[i].rchild = s2;

HT[i].weight = HT[s1].weight + HT[s2].weight;

HT[s1].parent = HT[s2].parent = i;}

}

对n个字符进行编码。采用逆序编码的方法,即从叶子结点出发,寻找根结点。例如第i个叶子结点中存储着它的双亲信息,访问第i个叶子节点的双亲p(注:双亲其实就是教材中的父节点,它是一个节点,而不是两个),判断此叶子结点是它双亲结点的左孩子还是右孩子,若为左孩子,则编码为0,右孩子编码为1。下一步继续往上访问,直到访问到根结点结束。

char * * Huf_code(Node * HT, int n) {

char * cd = (char *)malloc(n * sizeof(char));

char * * HC = (char * *)malloc((n + 1) * sizeof(char *)); //此处注意分配的char *类型的存储空间,而不是char类型

cd[n - 1] = '\0'; //所有字符的编码长度不会超过(n - 1)

int p; start; c;

for (int i = 1; i <= n; i++) {

c = i;start = n - 1;p = HT[i].parent;

while (p != 0) {

if (HT[p].lchild == c)

cd[--start] = '0';

if (HT[p].rchild == c)

cd[--start] = '1';

c = p;

p = HT[c].parent;

}

HC[i] = (char *)malloc((n - start) * sizeof(char));

strcpy(HC[i], &cd[start]);

return HC;}

读取文章,生成此文章的Huffman编码。每读到一个字符,先判断此字符的编码在数组HC中位置,再把编码写入到文件b.txt中。

void read_creat_code(char * * HC, int n, char * ture_form) {

FILE * fp_r = fopen("a.txt", "r");

FILE * fp_w = fopen("b.txt", "w");

char c = fgetc(fp_r);

int i, j;

while (c != EOF) {

for (i = 1; i <= n; i++) {

if (c == ture_form[i]) //判断当前读入的字符在原码中的位置

break;}

c = HC[i][0];

for (j = 1; c != '\0'; j++) {

fputc(c, fp_w);

c = HC[i][j];

}

c = fgetc(fp_r);

}

fclose(fp_r);

fclose(fp_w);}

读取编码,进行译码过程。从b.txt文件中读取编码,每次读取一个字符(此字符为0或1),并把此字符存放在一个临时数组中。判断当前数组中的字符是否为有效的Huffman编码,若是,则翻译此编码,并把翻译得到的字符写到文件c.txt中,清空临时数组,继续读取b.txt中的下一个字符。若不是有效编码,则继续读取b.txt中的下一个字符。

void decode(char ** HC, int n, char ture_form[]) {

FILE * fpr, *fpw;

fpr = fopen("b.txt", "r");

fpw = fopen("c.txt", "w");

char code[10] = { 0 }; //默认所有字符的编码长度不超过9,(字符数组最后一个元素为'\0')

for (int i = 0; i < 10; i++) {

if ((code[i] = fgetc(fpr)) == EOF)

break; //已经读到文件的末尾

    int state; //若当前编码无效,则state为0,否则为当前编码对应字符在数组ture_form中的位置

state = judge_valid(HC, code, n);

if (state != 0) { //若编码有效,则把对应字符写到文件里,并让下一个读到的赫夫曼码放到code[0]里面

fputc(ture_form[state], fpw);

i = -1;

for (int j = 0; j < 10; j++) //清空此临时数组

code[j] = '\0';}}

fclose(fpr);

fclose(fpw);

}

  1. 实验代码

    (即C语言程序

#include <stdlib.h>

#include<string.h>

#include <stdio.h>

#include <malloc.h>

struct Node {

int weight; // 权值

int parent, lchild, rchild;

};

//统计每个字符出现的次数

void Statis_char(int char_freque[]) {

FILE * fp;

fp = fopen("a.txt", "r");

char c;

while ((c = fgetc(fp)) != EOF ) {



if (c >= 97)

char_freque[c - 97]++;

else if (c >= 65)

char_freque[c - 39]++;

else if (c == ',')

char_freque[52]++;

else if (c == ' ')

char_freque[53]++;

else if (c == '.')

char_freque[54]++;

else //有的编译器可能无法使用exit函数,把此else语句注释掉就可以了

{

printf("非法字符\n");

// return 0;

}

}

fclose(fp);

}

//统计总共有多少种不同的字符

int Statis_n(int char_freque[]) {

int n = 0;

for (int i = 0; i < 55; i++)

if (char_freque[i] != 0)

n++;

return n;

}

//对现在的m棵树进行初始化

void Init_tree(Node HT[], char ture_form[], int char_freque[], char character[], int m) {

int i, j;

for (i = 0, j = 1; i < 55; i++)

if (char_freque[i] != 0) {

HT[j] = { char_freque[i],0,0,0 };

ture_form[j] = character[i]; //第j个叶子结点对应的字符为ture_form[j]

j++;

}

for (; j <= m; j++)

HT[j] = { 0,0,0,0 };



}

//从前k个结点中选取两个parent为0且weight最小的结点

void Select(Node * HT, int k, int * i, int * j) {

int min1, min2;

min1 = min2 = 2000; //假设任何一个字符的权值都不超过2000.

for (int t = 1; t <= k; t++) {

if (HT[t].parent == 0) {

if (HT[t].weight < min1) {

min1 = HT[t].weight;

*i = t;

}

else if (HT[t].weight < min2) {

min2 = HT[t].weight;

*j = t;

}

}

}

}

//建立赫夫曼树

void Creat_HT(Node * HT, int n, int m) {

int i;

int  s1, s2;

for (i = n + 1; i <= m; i++) {

Select(HT, i - 1, &s1, &s2);

HT[i].lchild = s1;

HT[i].rchild = s2;

HT[i].weight = HT[s1].weight + HT[s2].weight;

HT[s1].parent = HT[s2].parent = i;

}



}

//对每一个字符进行编码,逆序编码

char * * Huf_code(Node * HT, int n) {

char * cd = (char *)malloc(n * sizeof(char));

char * * HC = (char * *)malloc((n + 1) * sizeof(char *)); //此处注意分配的char *类型的存储空间,而不是char类型

cd[n - 1] = '\0'; //所有字符的编码长度不会超过(n - 1)

int p;

int start;

int c;

for (int i = 1; i <= n; i++) {

c = i;

start = n - 1;

p = HT[i].parent;

while (p != 0) {

if (HT[p].lchild == c)

cd[--start] = '0';

if (HT[p].rchild == c)

cd[--start] = '1';

c = p;

p = HT[c].parent;

}

HC[i] = (char *)malloc((n - start) * sizeof(char));



strcpy(HC[i], &cd[start]);



}

return HC;

}

//读取文章,并生成此文章的赫夫曼编码到文件b.txt中。

void read_creat_code(char * * HC, int n, char * ture_form) {

FILE * fp_r = fopen("a.txt", "r");

FILE * fp_w = fopen("b.txt", "w");

char c = fgetc(fp_r);

int i, j;

while (c != EOF) {

for (i = 1; i <= n; i++) {

if (c == ture_form[i]) //判断当前读入的字符在原码中的位置

break;

}

c = HC[i][0];

for (j = 1; c != '\0'; j++) {

fputc(c, fp_w);

c = HC[i][j];

}

c = fgetc(fp_r);

}

fclose(fp_r);

fclose(fp_w);

}

//判断当前存储在数组code中的编码是否为有效编码

int judge_valid(char ** HC, char code[], int n) {

for (int i = 1; i <= n; i++)

if (strcmp(code, HC[i]) == 0)

return i;

return 0;

}

//译码

void decode(char ** HC, int n, char ture_form[]) {

FILE * fpr, *fpw;

fpr = fopen("b.txt", "r");

fpw = fopen("c.txt", "w");

char code[10] = { 0 }; //默认所有字符的编码长度不超过9,(字符数组最后一个元素为'\0')

for (int i = 0; i < 10; i++) {

if ((code[i] = fgetc(fpr)) == EOF)

break; //已经读到文件的末尾

int state; //若当前编码无效,则state为0,否则为当前编码对应字符在数组ture_form中的位置

state = judge_valid(HC, code, n);

if (state != 0) { //若编码有效,则把对应字符写到文件里,并让下一个读到的赫夫曼码放到code[0]里面

fputc(ture_form[state], fpw);

i = -1;

for (int j = 0; j < 10; j++) //清空此临时数组

code[j] = '\0';

}

}

fclose(fpr);

fclose(fpw);

}

int main()

{



int n; //总共出现了n种不同的字符

int m; //所需总的结点个数

int i; //临时变量

char * ture_form; //文章中出现的字符用此数组存储

char character[55]; //存储所有可能出现的字符

int char_freque[55] = { 0 }; //记录每个字符出现的次数

Node * HT; //结点类型的指针,用来指向Huffman树的m个结点

//初始化文章中所有可能出现的字符

for (i = 0; i <= 25; i++)

character[i] = i + 97;

for (i = 26; i <= 51; i++)

character[i] = i + 39;

character[52] = ',';

character[53] = ' ';

character[54] = '.';



Statis_char(char_freque); //统计每个字符出现的次数

n = Statis_n(char_freque); //统计总共出现了多少种不同的字符

m = 2 * n - 1; //构建此Huffman树所需的节点个数

HT = (Node *)malloc((m + 1) * sizeof(Node)); //第零个存储空间不用

ture_form = (char *)malloc((n + 1) * sizeof(char)); //共有n种不同的字符,这n种字符用数组ture_form存储,第零个存储空间不用

Init_tree(HT, ture_form, char_freque, character, m); //初始化Huffman树的每个结点

Creat_HT(HT, n, m); //建立Huffman树

char * * HC; //HC[i]指向第i个字符的Huffman编码

HC = (char * *)malloc((n + 1) * sizeof(char *)); //第0个存储空间不用

HC = Huf_code(HT, n); //对n个字符进行编码

read_creat_code(HC, n, ture_form); //读取文章,并对整篇文章进行编码

decode(HC, n, ture_form); //读取Huffman编码并译码

}

  1. 算法测试结果

    (说明测试数据,粘贴实验结果图

 

 

 

  1. 分析与总结

    (1)算法复杂度分析及优、缺点分析

        (说明你编写算法的复杂度,算法的优点和缺点有哪些

文章中只能含有52个大小写字母,逗号,空格,句号这55种常用字符。其余的符号太多,未写进程序。文章不能太长,因为我默认所有字符的权值不超过2000,Huffman编码长度不超过9位

1.将所有结点放入集合 K。若集合 K 中剩余结点大于 2 个,则取出其中权值最小的两个结点,构造他们同时为某个新节点的左右儿子,该新节点是他们共同的双亲结点,设定它的权值为其两个儿子结点的权值和。并将该父亲结点放入集合 K。重复步骤。若集合 K 中仅剩余一个结点,该结点即为构造出的哈夫曼树数的根结点, 所有构造得到的中间结点(即哈夫曼树上非叶子结点)的权值和即为该哈夫曼树的带权路径和。为了方便快捷高效率的求得集合 K 中权值最小的两个元素,我们需要使用堆数据结构。它可以以 O(logn)的复杂度取得 n 个元素中的最小元素。

    (2)实验总结

        (说明你怎么解决实验中遇到的问题,有什么收获

为了避免a.txt文件加错位置而引发断点,更新后的程序中添加了判断文件是否成功打开的代码,若文件打开失败,则输出提示信息,并退出程序的执行。熟悉掌握Huffman树的构造方法及Huffman编码的应用,了解Huffman树在通信、编码领域的应用过程。同时回顾前边的对于文件的操作和使用,巩固了知识

  • 14
    点赞
  • 91
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Huffman树是一种用于数据压缩的算法,它通过构建一棵二叉树来实现。在构建Huffman树的过程中,我们需要先将待压缩的数据按照出现频率从小到大排序,然后将频率最小的两个数据合并成一个节点,该节点的权值为两个数据的权值之和。接着,将新节点插入到已排序的数据中,重新排序,重复以上步骤,直到只剩下一个节点,这个节点就是Huffman树的根节点。 Huffman编码是一种将字符编码为二进制的算法,它利用Huffman树的结构来实现。在Huffman树中,左子树表示,右子树表示1,从根节点到叶子节点的路径就是该字符的编码编码的长度取决于该字符在Huffman树中的深度,出现频率越高的字符编码越短。 Huffman编码算法实现步骤如下: 1. 统计每个字符出现的频率,并按照频率从小到大排序。 2. 将频率最小的两个字符合并成一个节点,该节点的权值为两个字符的权值之和。 3. 将新节点插入到已排序的字符中,重新排序。 4. 重复步骤2和3,直到只剩下一个节点,这个节点就是Huffman树的根节点。 5. 遍历Huffman树,生成每个字符编码。 6. 将编码存储起来,用于解码时的还原。 以上就是Huffman树及Huffman编码算法实现。 ### 回答2: Huffman树是一种用来进行数据压缩的算法,它基于字符出现的频率来构建一棵树,从而生成一种特殊的编码方式——Huffman编码Huffman编码是一种非常高效的压缩方式,可以让数据在传输或存储时占用更少的空间。 Huffman树的构建过程非常简单,可以分为以下几步: 1. 统计每个字符出现的频率,根据频率从小到大排序。 2. 取出频率最小的两个字符,合并成一个新节点,并将新节点的频率设置为这两个字符的频率之和。新节点的左节点为频率小的字符,右节点为频率大的字符。 3. 重复步骤2,直到只剩下一个节点,此节点即为Huffman树的根节点。 在构建Huffman树完成后,就可以得到每个字符Huffman编码Huffman编码的规则是:左子节点表示0,右子节点表示1。从根节点开始,沿着每个字符所在路径的方向记录0或1,然后组成一个二进制数就是该字符的Huffman编码。 在实现Huffman编码时,我们可以通过一个哈希表来存储每个字符出现频率,然后将哈希表中的数据插入到一个优先队列中,优先队列中的元素按照频率从小到大排序。接下来,我们可以按照上述步骤来构建Huffman树,最后得到每个字符Huffman编码。我们可以将Huffman编码存储到另一个哈希表中,这样我们就可以将原始数据按照Huffman编码来进行压缩了。 Huffman编码是一种很好的数据压缩方式,它可以大大减少数据在传输和存储时所占用的空间。Huffman树的构建和Huffman编码实现并不难,只需要遵循一定的规则,就可以得到正确的结果。 ### 回答3: Huffman树是一种基于贪心算法的数据压缩方式,通过构建哈夫曼树(也称最优二叉树),来实现对数据的压缩和解压缩。其编码方式被广泛应用于压缩文件、图像和视频等多种媒体数据。 哈夫曼树是一种带权树(也称为加权树),每个节点带有权值(也称为频率),树根到叶子节点的路径表示一个字符编码哈夫曼树的构造需要完成三个步骤: 1. 将给定字符集中的每个字符,按照出现频率从小到大进行排序; 2. 依次选取出现频率最小的两个字符,创建一个新节点,将这两个节点作为其子节点,将新节点的权值设为这两个节点的权值之和; 3. 重复步骤2,直到只剩下一个节点,此节点即为哈夫曼树的根节点。 构造好哈夫曼树后,通过遍历树,得到每个字符编码。具体方法是从根节点开始,如果遇到左子节点就在当前编码的末尾添加0,如果遇到右子节点就添加1,直到到达叶子节点,此时就得到了该字符对应的编码。在编码时,为了避免出现字符编码相同的情况,要求每个字符编码不是任何一个字符编码的前缀。 Huffman编码实现主要有两个步骤:构建哈夫曼树和生成编码。在构建哈夫曼树时,需要使用堆这个数据结构来维护出现频率最小的两个字符。在生成编码时,可以通过深度优先遍历递归来实现。 使用哈夫曼编码可以将一组字符压缩成相应的位序列,可以大大减小存储和传输数据所需的空间和时间。然而,哈夫曼编码虽然可以解决数据压缩的问题,但一旦数据被压缩,就需要解压缩才能使用,而解压缩的过程中会消耗一定的时间和计算资源。此外,当压缩目标文件内容比较零散,分布比较分散时,使用哈夫曼编码并不能得到较好的压缩效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值