一、实验目的
熟练掌握huffman树的构建方法以及huffman编码。
二、预备知识
1. 哈夫曼树的存储结构
typedef struct
{
unsigned int weight;
unsigned int parent, lchild, rchild;
}HTNode,*HuffmanTree; //动态分配数组存储哈夫曼树
2. 哈夫曼编码的存储结构
typedef char * *HuffmanCode; //动态分配数组存储哈夫曼编码表
三、实验题目
从键盘接收任意一个字符串。以字符串中某字符出现的次数,作为该字符的权值。利用得到的权值构造huffman树、并输出每个字符对应的huffman编码。
四、实验要求
1)哈夫曼树和哈夫曼编码数据类型定义、select()函数(求两最小权值结点)、哈夫曼树构造、求编码函数、字符串输入处理函数等的声明放在HuffmanDef.h文件;
2)相关预编译命令等放在公共头文件CommonDef.h文件;
3)select()函数、哈夫曼树构造函数、哈夫曼编码函数的实现可放在Huffman.c文件;
4)测试程序放在HuffmanTestApp.c中。
五、需求分析
本实验通过C语言来实现huffman树的构建方法以及huffman编码。
- 从键盘接收任意一个字符串。以字符串中某字符出现的次数,作为该字符的权值。利用得到的权值构造huffman树、并输出每个字符对应的huffman编码。
本程序没有边界约束,违反输入规则的均会导致程序运行失败。
六、概要设计
1.抽象数据类型的定义
哈夫曼树的存储结构
typedef struct
{
unsigned int weight;
unsigned int parent, lchild, rchild;
}HTNode,*HuffmanTree; //动态分配数组存储哈夫曼树
哈夫曼编码的存储结构
typedef char * *HuffmanCode;//动态分配数组存储哈夫曼编码表
2.主程序的流程图
3.程序各功能模块调用关系图
七、详细设计
1.程序开始预编译部分如下:
//头文件
#include <stdio.h>
#include "HuffmanDef.h"
#include <stdlib.h>
#include <string.h>
#define ARRAY_MAX_SIZE 100//存储输入字符的最大数
//定义类型
typedef struct {
unsigned int weight;
unsigned int parent, lchild, rchild;
} HTNode, *HuffmanTree; //动态分配数组存储哈夫曼树
typedef char **HuffmanCode;//动态分配数组存储哈夫曼编码表
2.程序主要功能函数如下:
void Select(HuffmanTree HT, int a, int *p1, int *p2);//Select函数,选出HT树到a为止,权值最小且parent为0的2个节点
void HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC);//构建哈夫曼树HT,并求出哈夫曼编码HC
int InputString(int (*ch)[ARRAY_MAX_SIZE], int (*num)[ARRAY_MAX_SIZE]);//统计输入的字符和个数,默认用静态分配内存空间
void test();//测试函数
3.函数调用关系图
八、调试分析
- 将哈夫曼树的构建和编码合成到一个函数HuffmanCoding中,其实是可以分开的,但我考虑到,哈夫曼编码时需要哈夫曼树的节点数,我又不想传参,也不想单独写一个测结点数量的函数,还有构建树和编码有很多参数都会被重复用到,所以就合并成一个函数了,主要是方便了自己,不需要重构代码,偷了个懒。
- 测输入字符串中字符的种类和数量(权值),我采用两个数组来存储,一个存放字符,一个存放字符的权值,两个数组的下标一一对应。
为啥使用数组存储?偷个懒,可以用动态分配存储空间。
为啥用两个数组?一是为了方便储存,二更主要是考虑到,编码大部分输入的字符串会很长,使用两个数组可以在测数量时循环只做一次,牺牲空间来换取时间。
默认两对数组(存储字符串信息的两个数组和HT、HC)第0位置不使用的原因是:在构建哈夫曼树时要做很多循环,主要还是为了对好结点的下标,第一结点就是数组的第一元素,增强代码的可读性,也怕自己搞混,同样的,HT和HC的第0位置的存储空间也是默认不使用的,统一下标。
九、测试数据与结果
本次实验没有考虑到输入边界的问题,本次实验的主要目的是实验哈夫曼编码的功能,故没有测试环节,下面是功能实现。
1.运行开始
2.输入字符串(以回车键结束):
aaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccddddddddeeeeeeeeeeeeeefffffffffffffffffffffffggghhhhhhhhhhh+回车键
附录——源代码清单
CommonDef.h
#include <stdio.h>
#include "HuffmanDef.h"
#include <stdlib.h>
#include <string.h>
HuffmanDef.h
#define ARRAY_MAX_SIZE 100//存储输入字符的最大数
typedef struct {
unsigned int weight;
unsigned int parent, lchild, rchild;
} HTNode, *HuffmanTree; //动态分配数组存储哈夫曼树
typedef char **HuffmanCode;//动态分配数组存储哈夫曼编码表
extern void Select(HuffmanTree HT, int a, int *p1, int *p2);//Select函数,选出HT树到a为止,权值最小且parent为0的2个节点
extern void HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC);//构建哈夫曼树HT,并求出哈夫曼编码HC
extern int InputString(int (*ch)[ARRAY_MAX_SIZE], int (*num)[ARRAY_MAX_SIZE]);//统计输入的字符和个数,默认用静态分配内存空间
extern void test();//测试函数
Huffman.c
#include "CommonDef.h"
int InputString(int (*ch)[ARRAY_MAX_SIZE], int (*num)[ARRAY_MAX_SIZE]) {//统计输入的字符和个数,默认用静态分配内存空间
int length = 0;
int i;
int c;//输入的字符
printf("请输入要编码的字符串:\n");
while ((c = getchar()) != '\n') {
for (i = 0; i < length; ++i) {
if (c == (*ch)[i + 1]) {
(*num)[i + 1] += 1;
break;
}
}
if (i == length) {
(*ch)[length + 1] = c;
(*num)[length + 1] = 1;
length++;
}
}
printf("字符对应的权值如下:\n");
for (i = 1; i < length + 1; ++i)
printf("%c %d\n", (*ch)[i], (*num)[i]);
return length;
}
void Select(HuffmanTree HT, int a, int *p1, int *p2) {//Select函数,选出HT树到a为止,权值最小且parent为0的2个节点
int i, j, x, y, count, temp;
for (j = 1, count = 1; j <= a; j++) {
if (HT[j].parent == 0) {
if (count == 1)
x = j;
if (count == 2)
y = j;
count++;
}
if (count > 2)
break;
}
if (HT[x].weight > HT[y].weight)//令x结点权值小于y结点权值
{
temp = y;
y = x;
x = temp;
}
i = (x > y ? x : y) + 1;
while (i <= a) {
if (HT[i].parent == 0) {
if (HT[i].weight < HT[x].weight) {
y = x;
x = i;
} else {
if (HT[i].weight >= HT[x].weight && HT[i].weight < HT[y].weight)
y = i;
}
}
i++;
}
*p1 = HT[x].weight <= HT[y].weight ? x : y;
*p2 = HT[x].weight > HT[y].weight ? x : y;
}
void HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC)//构建哈夫曼树HT,并求出n个字符的哈夫曼编码HC
{
int i, start, c, f, m;
int length;//字符串长度
int ch[ARRAY_MAX_SIZE];//存储输入的字符数组
int num[ARRAY_MAX_SIZE];//存储输入的字符的个数的数组
int p1, p2;
char *cd;
length = InputString(&ch, &num);
if (length <= 1)
exit(1);
m = 2 * length - 1;//n个叶子结点的哈夫曼树共有2n-1个结点
*HT = (HuffmanTree) malloc((m + 1) * sizeof(HTNode));//0号单元未使用
for (i = 1; i <= length; i++)//初始化n个叶子结点
{
(*HT)[i].weight = num[i];
(*HT)[i].parent = 0;
(*HT)[i].lchild = 0;
(*HT)[i].rchild = 0;
}
for (i = length + 1; i <= m; i++)//初始化其余结点
{
(*HT)[i].weight = 0;
(*HT)[i].parent = 0;
(*HT)[i].lchild = 0;
(*HT)[i].rchild = 0;
}
for (i = length + 1; i <= m; i++)//建立哈夫曼树
{
Select((*HT), i - 1, &p1, &p2);
(*HT)[p1].parent = i;
(*HT)[p2].parent = i;
(*HT)[i].lchild = p1;
(*HT)[i].rchild = p2;
(*HT)[i].weight = (*HT)[p1].weight + (*HT)[p2].weight;
}
//打印Huffman表
printf("Huffman表如下:\n");
for (i = 1; i < m + 1; ++i) {
printf("<%-2i>%4d%4u%4u%4u\n", i, (*HT)[i].weight, (*HT)[i].parent, (*HT)[i].lchild, (*HT)[i].rchild);
}
//从叶子到根逆向求每个字符的哈夫曼编码
*HC = (HuffmanCode) malloc((length + 1) * sizeof(char *));
cd = (char *) malloc(length * sizeof(char));
cd[length - 1] = '\0';
printf("对应的哈夫曼编码如下:\n");
for (i = 1; i <= length; i++) {
start = length - 1;
for (c = i, f = (int) (*HT)[i].parent; f != 0; c = f, f = (int) (*HT)[f].parent) {
if ((*HT)[f].lchild == c)
cd[--start] = '0';
else
cd[--start] = '1';
}
(*HC)[i] = (char *) malloc((length - start) * sizeof(char));
strcpy((*HC)[i], &cd[start]);
printf("第%d个字符对应的Huffman编码:%s\n", i, (*HC)[i]);
}
free(cd);
}
int main() {
test();
return 0;
}
HuffmanTestApp.c
#include "HuffmanDef.h"
void test(){//测试函数
HuffmanTree HT;
HuffmanCode HC;
HuffmanCoding(&HT, &HC);
}