一、功能需求
设计并实现一个写一个哈夫曼码的编/译码系统,系统功能包括:
(1)I:初始化(Initialization)。
从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmTree中;
(2)E:编码(Encoding)。
利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中;
(3)D:译码(Decoding)。
利用已建好的哈夫曼树将文件CodeFile中的代码进行译码,结果存入文件TextFile中;
(4)P:打印代码文件(Print)。
将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrint中;
(5)T:印哈夫曼树(Tree printing)。
将已在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
二、界面需求
打印出哈弗曼编码指令的界面,并显示程序进程与错误信息,编码解码与树的界面打印信息。
三、概要设计
四、代码实现
1. 必要库引入
#include<stdlib.h>
#include <stdio.h>
#include <string.h>
#include <Windows.h>
2. 宏变量定义
#define POS_X1 35 //打印菜单定位光标
#define POS_X3 50
#define POS_X4 60
#define MAX_LENGTH 300 //哈夫曼树元素编码后的字符串长度
3. 基本数据结构定义
我们需要定义哈夫曼树节点,每个节点存储对应的元素及权值,同时存储他的左右子节点,由于后面要用到队列指针,同时也需要存储后节点
定义优先队列,后续读取时需要用到
typedef char E;
typedef int T;
//哈夫曼树节点
typedef struct TreeNode {
E element; //元素
T value; //权值
struct TreeNode * left; //左字节点
struct TreeNode * right; //右字节点
struct TreeNode * next; //队列指针
} * Node;
//优先队列
typedef struct Queue {
Node front, rear; //构建优先队列
} * LinkedQueue;
4. 函数声明
后续会用到的所有函数如下
//函数声明
void SetPosition(int x, int y); //定位光标位置
int Menu(void); //打印菜单
int InitQueue(LinkedQueue queue); //初始化优先队列
int OfferQueue(LinkedQueue queue, T value, E element); //优先队列入队
Node PollQueue(LinkedQueue queue); //优先队列出队
Node CreateNode(E element, T value); //创建哈夫曼树节点
int OfferNode(LinkedQueue queue, Node node); //哈夫曼树节点入队优先队列
void CreatHfmTree(LinkedQueue queue, int n); //构造哈夫曼树
int OfferQueueP(LinkedQueue queue, Node root); //临时优先队列入队
int IsEmpty(LinkedQueue queue); //判断队列是否为空
void LevelOrderToFile(Node root, FILE *f); //层序遍历哈夫曼树写入文件
Node ReadHfmTree(LinkedQueue queue); //从文件读取哈夫曼树
char * EnCodeSingle(Node root, E e); //编码单个字符e
void PrintEncode(Node root, E e); //将编码字符写入文件
void EnCoding(Node root); //译码文件
void DeCoding(Node root); //译码文件
void PrePrint(Node root, int num); //前序遍历打印哈夫曼树凹入表
void CodeFilePrint(Node root); //读取编码文件译码后写入文件
5. 定位光标函数
调用库函数,实现光标定位,打印菜单
void SetPosition(int x, int y) {
HANDLE hout;
COORD pos;
hout = GetStdHandle(STD_OUTPUT_HANDLE);
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(hout, pos);
}
6. 打印主页面菜单
调用光标位置函数打印具体功能,用户键入不同数字实现不同功能
int Menu(void) {
int posy = 5;
int option;
int i, j;
SetPosition(POS_X3, posy);
printf("哈夫曼树编码\n");
for (i = 0; i < 2; i++) {
SetPosition(POS_X1, ++posy);
for (j = 0; j < 55; j++) {
printf("-");
}
}
SetPosition(POS_X1, ++posy);
printf("1.构建哈夫曼树");
SetPosition(POS_X4, posy);
printf("2.编码单个元素");
SetPosition(POS_X1, posy += 2);
printf("3.编码文件");
SetPosition(POS_X4, posy);
printf("4.译码文件");
SetPosition(POS_X1, posy += 2);
printf("5.打印哈夫曼树");
SetPosition(POS_X4, posy);
printf("6.读取哈夫曼树");
SetPosition(POS_X1, posy += 2);
printf("7.打印代码文件");
SetPosition(POS_X4, posy);
printf("0.退出");
for (i = 0; i < 2; i++) {
SetPosition(POS_X1, ++posy);
for (j = 0; j < 55; j++) {
printf("-");
}
}
SetPosition(POS_X1, ++posy);
printf("请选择你的操作[0~7]:[ ]\b\b");
scanf("%d", &option);
return option;
}
7. 哈夫曼树的实现
先放最后封装好的构建函数,接下来我们一步步看如何实现的
首先,用户输入要构建几个节点 n
,然后我们使用 for
循环持续读取 n
个节点信息,将节点信息存储起来。但是考虑到哈夫曼树的性质,我们夫节点绝对是大于子节点的 value
的,而我们用户输入的数据并不能保持有序性,可能是大小随机的,如 7、12、1、4、6
,所以我们需要考虑将用户输入的数据进行统一的排列,此时就需要用到优先队列,我们可以将用户输入的每个节点都存储起来,构建优先队列,保持从小到大的顺序,如 1、4、6、7、12
,这样我们在后续构建哈夫曼树时,可以依次取出最小的节点来构建
for
循环完成后我们成功构建出优先队列,此时开始构建哈夫曼树。我们将优先队列中的数值依次取出,构建节点,取出两个小节点后,将两个左右节点的数值相加得到父节点数值,再将父节点存入到优先队列,多次遍历后实现哈夫曼树的构建
//创建哈夫曼树
void CreatHfmTree(LinkedQueue queue, int n) {
T value;
E element;
for (int i = 0; i < n; ++i) {
printf("请输入节点权值及名称:");
scanf("%d %c", &value, &element);
OfferQueue(queue, value, element);
}
while (queue->front != NULL && queue->front->next != queue->rear) {
//
Node left = PollQueue(queue);
Node right = PollQueue(queue);
Node node = CreateNode(' ', left->value + right->value); //
node->left = left;
node->right = right;
OfferNode(queue, node);
}
printf("哈夫曼树创建成功!\n");
getchar();
}
接下来我们依次看各个函数的实现:
初始化优先队列
首先我们需要初始化优先队列,传入节点后先开辟内存空间,然后将队列的前指针和后指针都指向自身,置空节点的左右子节点及后节点
//初始化优先队列
int InitQueue(LinkedQueue queue) {
Node node = (Node)malloc(sizeof(struct TreeNode));
if(node == NULL) return 0;
queue->front = queue->rear = node;
node->left = node->right = NULL;
node->next = NULL; //置空
return 1;
}
优先队列入队
然后要考虑怎么将节点插入进来,构建优先队列:
传入节点后,先将节点赋值,然后将节点的左右节点及后节点置空,然后在优先队列中寻找合适的位置,插入到合适顺序,保持优先队列由大到小排列
//进优先队列
int OfferQueue(LinkedQueue queue, T value, E element){
Node node = (Node)malloc(sizeof(struct TreeNode));
if(node == NULL) return 0;
node->element = element;
node->value = value;
node->next = NULL;
node->left = node->right = NULL;
Node pre = queue->front;
while (pre->next && pre->next->value <= value) //插入到合适的位置
pre = pre->next;
if(pre == queue->rear) {
queue->rear->next = node;
queue->rear = node;
} else {
node->next = pre->next;
pre->next = node;
}
return 1;
}
优先队列出队
和正常队列一样,取出头节点即可,记得判断是不是最后一个节点,如果是需要将头尾指针对齐
//出队
Node PollQueue(LinkedQueue queue){
Node node = queue->front->next;
queue->front->next = queue->front->next->next; //直接取出
if(queue->rear == node) queue->rear = queue->front; //判断是不是最后一个
return node;
}
创建哈夫曼树节点
创建哈夫曼树节点,存储传入的元素及权值,注意将左右节点置空即可
//创建哈夫曼树节点
Node CreateNode(E element, T value){
Node node = (Node)malloc(sizeof(struct TreeNode));
node->element = element;
node->value = value;
node->left = node->right = NULL;
return node;
}
将哈夫曼树节点入队
将哈夫曼树节点入队,插入到优先队列中即可,有同学可能会问,这为什么不调用之前的优先队列入队函数 offerQueue(LinkedQueue queue, T value, E element)
呢?
//控制台输入数据进优先队列
int OfferQueue(LinkedQueue queue, T value, E element){
Node node = (Node)malloc(sizeof(struct TreeNode));
if(node == NULL) return 0;
node->element = element;
node->value = value;
node->next = NULL;
node->left = node->right = NULL;
...
...
}
其实认真观察即可发现,在offerQueue
函数中,传入的value
element
后,是重新新建了一个节点,如果我们调用这个方法,将无法保存我们在上一步构建的左右子节点关系,所以我们需要重新封装一个函数,传入我们的 node
节点进去来进入优先队列
//将哈夫曼树节点入队
int OfferNode(LinkedQueue queue, Node node){
node->next = NULL;
Node pre = queue->front;
while (pre->next && pre->next->value <= node->value) //
pre = pre->next;
if(pre == queue->rear) {
queue->rear->next = node;
queue->rear = node;
} else {
node->next = pre->next;
pre->next = node;
}
return 1;
}
构建哈夫曼树
最后将封装好的函数依次调用即可,整理后如下:
//创建哈夫曼树
void CreatHfmTree(LinkedQueue queue, int n) {
T value;
E element;
for (int i = 0; i < n; ++i) {
printf("请输入节点权值及名称:");
scanf("%d %c", &value, &element);
OfferQueue(queue, value, element);
}
while (queue->front != NULL && queue->front->next != queue->rear) {
//
Node left = PollQueue(queue);
Node right = PollQueue(queue);
Node node = CreateNode(' ', left->value + right->value); //
node->left = left;
node->right = right;
OfferNode(queue, node