哈夫曼编、译码系统
一、目的
通过哈夫曼编、译码算法的实现,巩固二叉树及哈夫曼树相关知识的理解掌握,训练运用所学知识,解决实际问题的能力。
学习使用C语言编写哈夫曼编、译码系统,并掌握其基本原理和步骤。
了解哈夫曼编码在数据压缩和通信中的应用和优势。
二、要求
1.题目要求:
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发编写一个哈夫曼码的编/译码系统。
2.基本要求:
①接收原始数据(电文):从终端输入电文(电文为一个字符串,假设仅由26个小写英文字母构成)。
②编码:利用已建好的哈夫曼树,对电文进行编码。
③打印编码规则:即字符与编码的一一对应关系。
④打印显示电文以及该电文对应的哈夫曼编码。
⑤接收原始数据(哈夫曼编码):从终端输入一串二进制哈夫曼编码(由0和1构成)。
⑥译码:利用已建好的哈夫曼树对该二进制编码进行译码。
⑦打印译码内容:将译码结果显示在终端上。
三、实验方案设计
1.数据结构设计
定义两个结构体分别存储结点的字符及权值、哈夫曼编码值:
typedef struct { char ch; //字符
float weight; //权值
int lchild, rchild; //左右孩子
int parent; //双亲 }hufmtree; // 存储结点的字符及权值
typedef struct {
char bits[n]; //位串
int start; //编码在位串中的起始位置
char ch; //字符 }codetype; //存储哈夫曼编码值
定义两个全局变量分别表示计算过程中的两个大数:
hufmtree tree[M]; //M为最大结点数
codetype code[N]; //N为最大字符数
2.算法设计思路:
2.1构造两个结构体分别存储结点的字符及权值、哈夫曼编码值:
typedef struct {
char ch; //字符
float weight; //权值
int lchild, rchild; //左右孩子
int parent; //双亲
}hufmtree; // 存储结点的字符及权值
typedef struct {
char bits[n]; //位串
int start; //编码在位串中的起始位置
char ch; //字符
}codetype; //存储哈夫曼编码值
2.2.构造最优二叉树,即哈夫曼树:
输入字符集大小n,以及n个字符和n个权值;
初始化n个单节点树,每个节点存储一个字符和一个权值;
循环执行以下操作,直到只剩下一个根节点:
在当前所有节点中选择两个权值最小且无双亲的节点;
将这两个节点合并为一个新节点,新节点的权值为两个子节点权值之和,新节点作为两个子节点的双亲;
将新节点加入到当前所有节点中;
输出最优二叉树。
根据最优二叉树生成哈夫曼编码:
初始化n个空位串,每个位串对应一个字符;
对于每个叶子节点,从下往上回溯其路径,记录其经过的左右分支:
如果经过左分支,则在位串前加入0;
如果经过右分支,则在位串前加入1;
直到回溯到根节点为止;
输出每个字符对应的位串。
2.3根据最优二叉树进行哈夫曼编码:
输入一个字符串,作为待编码电文;
对于每个字符,在已生成的位串中查找其对应的哈夫曼编码;
将所有字符的哈夫曼编码拼接起来,作为输出结果。
根据最优二叉树进行哈夫曼译码:
输入一串二进制数字,作为待译码电文;
从左到右扫描该数字串,并从根节点开始沿着最优二叉树向下移动:
如果遇到0,则向左移动一步;
如果遇到1,则向右移动一步;
如果到达一个叶子节点,则输出该节点对应的字符,并返回到根节点重新开始;
直到扫描完整个数字串为止。
2.4程序功能模块及逻辑调用关系、关键算法流程图:
st0=>start: start 哈夫曼编、译码系统
io1=>inputoutput: input: 输入字符集大小、字符和权值
sub2=>subroutine: 构造最优二叉树
sub3=>subroutine: 生成哈夫曼编码
sub4=>subroutine: 进行哈夫曼编码
sub5=>subroutine: 进行哈夫曼译码
io6=>inputoutput: output: 输出编码结果和译码结果
e7=>end: end 结束
st0->io1
io1->sub2
sub2->sub3
sub3->sub4
sub4->sub5
sub5->io6
io6->e7
st9=>start: start 构造最优二叉树
op10=>operation: 初始化n个单节点树
cond11=>condition: 是否只剩下一个根节点?
e14=>end: end 结束
op12=>operation: 选择权值最小的两个节点
op13=>operation: 合并为一个新节点,新节点的权值为两个子节点权值之和,新节点作为两个子节点的双亲,将新节点加入到当前所有节点中
st9->op10
…
cond11(no)->op12
op12->op13
op13->cond11
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings…
3.算法时间复杂度分析:
构造最优二叉树的时间复杂度为O(n^2),其中n为字符集大小;
根据最优二叉树生成哈夫曼编码的时间复杂度为O(nlogn),其中n为字符集大小;
根据最优二叉树进行哈夫曼编码和译码的时间复杂度均为O(mlogn),其中m为电文长度,n为字符集大小。
四、实验步骤或程序(源码)
经调试后正确的源程序:
#include<stdio.h>
#include<stdlib.h>
#define N 50
#define M 2*N
#define MAX 10000
#define FINISH 1
#define CONTINUE 0
#include<cstring>
#include<string.h>
typedef char ElemType;
typedef struct
{
int weight;
int Lchild, Rchild;
int parent;
ElemType data;
}HTNode, HuffmanTree[M];
typedef struct Code
{
struct Code *next, *prior;
int value;
}CodeNode, *pCode;
typedef struct
{
int code[100];
ElemType data;
}HFCode[100], HFC;
int n;
//int code[1000];
HuffmanTree ht;
HFCode hc;
char str[100];
int k=1;
int strl[100];
void CrtHuffmanTree();
void Select(int *s1, int *s2, int i);
void Input();
void Init();
void Init2();
int CrtHuffmanCode(int i, pCode BC);
void Printer();
int main()
{
pCode BC;
BC=(CodeNode*)malloc(sizeof(CodeNode));
BC->next=BC->prior=NULL;
Init();
Input();
CrtHuffmanTree();
Init2();
CrtHuffmanCode(2*n-1, BC);
Printer();
}
void Init()
{
for(int i=1; i<=2*n-1; i++)
{
ht[i].data='#';
ht[i].weight=0;
ht[i].parent=ht[i].Lchild=ht[i].Rchild=0;
}
}
void Input()
{
scanf("%d", &n);
getchar();
for(int i=1; i<=n; i++)
{
scanf("%c", &ht[i].data);
getchar();
}
for(int i=1; i<=n; i++)
{
scanf("%d", &ht[i].weight);
getchar();
}
fgets(str, 1000, stdin);
}
void Select(int *s1, int *s2, int i)
{
int m1, m2;
m1=m2=MAX;
for(int j=1; j<=i; j++)
{
if(ht[j].parent == 0)
{
if(ht[j].weight < m1)
{
m2=m1;
*s2=*s1;
*s1=j;
m1=ht[j].weight;
}
else if(ht[j].weight <m2)
{
*s2=j;
m2=ht[j].weight;
}
}
}
}
void CrtHuffmanTree()
{
int s1, s2;
for(int i=n+1; i<=2*n-1; i++)
{
Select(&s1, &s2, i-1);
ht[i].weight=ht[s1].weight+ht[s2].weight;
ht[s1].parent=ht[s2].parent=i;
ht[i].Lchild=s1;
ht[i].Rchild=s2;
}
}
void Init2()
{
for(int i=1; i<=M-10; i++)
{
for(int j=1; j<=90; j++)
hc[i].code[j]=-1;
}
}
int CrtHuffmanCode(int i, pCode BC)
{
if(i==0)
{
// k--;
return 0;
}
CodeNode *p, *s;
int par=ht[i].parent;
s=(CodeNode*)malloc(sizeof(CodeNode));
p=BC;
// p->next=s;
for(int j=1; j<= k; j++)
{
if(par==0)
{
k--;
break;
}
// s=(CodeNode*)malloc(sizeof(CodeNode));
// p->next=s;
s=(CodeNode*)malloc(sizeof(CodeNode));
if(j==k)
{
p->next=s;
p=p->next;
if(ht[par].Lchild==i)
p->value=0;
else p->value=1;
}
else p=p->next;
hc[i].code[j]=p->value;
hc[i].data=ht[i].data;
}
k++;
CrtHuffmanCode(ht[i].Lchild, BC);
p->value=1;
CrtHuffmanCode(ht[i].Rchild, BC);
k--;
return 0;
}
int Transfer(ElemType ch)
{
for(int i=1; i<=2*n-1; i++)
{
if(hc[i].data==ch)
return i;
}
return -1;
}
/*char* TTransfer(int )
{
for(int )
}
*/
void Printer()
{
int t, k=0, l;
l=strlen(str)-1;
for(int i=1; i<=l; i++)
{
t=Transfer(str[i-1]);
for(int j=1; j<=100; j++)
{
if(hc[t].code[j]==-1) break;
strl[k]=hc[t].code[j];
printf("%d",hc[t].code[j]);
}
}
printf("\n");
for(int i=0; i<l; i++)
{
printf("%c",str[i]);
}
printf("\n");
}
五、实验结果及分析
实验结果截图:
实验结果分析:
根据输入的字符集大小、字符和权值,程序能根据输入的字符集大小、字符和权值,程序能够正确地构造哈夫曼树,并打印出每个字符对应的哈夫曼编码;
根据输入的电文,程序能够正确地进行哈夫曼编码,并打印出编码结果;
根据输入的二进制编码,程序能够正确地进行哈夫曼译码,并打印出译码结果;
从实验结果可以看出,哈夫曼编码能够有效地压缩数据,使得电文的长度减少了一半以上;
从实验结果也可以看出,哈夫曼编码是一种无损压缩,即译码后的电文与原始电文完全相同,没有任何信息丢失。