◆5.2③哈夫曼编/译码器

**

◆5.2③哈夫曼编/译码器

**
[问题描述]
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成
本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数
据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的
编/译码系统。试为这样的信息收发站写一个哈夫曼码的编/译码系统。
[基本要求]
一个完整的系统应具有以下功能:
(1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值, .
建立哈夫曼树,并将它存于文件hfmTree中。
(2) E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
(3)D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译
码,结果存入文件TextFile中。
(4)P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代
码。同时将此字符形式的编码文件写入文件CodePrin中。
(5)T:印哈夫曼树(Treeprinting)。将已在内存中的哈夫曼树以直观的方式(树或凹
人表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
[测试数据]
(1)利用教科书例6-2中的数据调试程序。
(2)用下表给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的
编码和译码:“THIS PROGRAM IS MY FAVORITE"。
在这里插入图片描述
[实现提示]
(1)编码结果以文本方式存储在文件CodeFile中。
(2)用户界面可以设计为“菜单”方式:显示上述功能符号,再加上“Q”,表示退出运行Quit。请用户键入一个选择功能符。此功能执行完毕后再显示此菜单,直至某次用户选
择了“Q”为止。
(3)在程序的一次执行过程中,第一次执行I,D或C命令之后,哈夫曼树已经在内存
了,不必再读入。每次执行中不一定执行I命令,因为文件hfmTree可能早已建好。
[选作内容]
(1),上述文件CodeFile中的每个“0”或“1”实际上占用了一个字节的空间,只起到示
意或模拟的作用。为最大限度地利用码点存储能力,试改写你的系统,将编码结果以二进
制形式存放在文件CodeFile中。
(2)修改你的系统,实现对你的系统的原程序的编码和译码(主要是将行尾符编/译
码问题)。
(3)实现各个转换操作的源/目文件,均由用户在选择此操作时指定。

实习报告5.2题 哈夫曼编/译码

一、 需求分析

  1. 为信息收发站写一个哈夫曼码的编/译码系统
  2. (1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值, .
    建立哈夫曼树,并将它存于文件hfmTree中。
    (2) E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
    读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
    (3)D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译
    码,结果存入文件TextFile中。
    (4)P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代
    码。同时将此字符形式的编码文件写入文件CodePrin中。
    (5)T:印哈夫曼树(Treeprinting)。将已在内存中的哈夫曼树以直观的方式(树或凹
    人表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
  3. 用下表给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的
  4. 在这里插入图片描述

二、 概要设计
1)
Initialization()
//从终端读入字符集大小n,以及n个字符和n个权值, .
//建立哈夫曼树,并将它存于文件hfmTree中。
Encoding()
//利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
//读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
Decoding()
//利用已建好的哈夫曼树将文件CodeFile中的代码进行译
//码,结果存入文件TextFile中。
Print()
//将文件CodeFile以紧凑格式显示在终端上,每行50个代
//码。同时将此字符形式的编码文件写入文件CodePrin中。
Treeprinting()
//将已在内存中的哈夫曼树以直观的方式(树或凹人表形式)显示在终端上,同时//将此字符形式的哈夫曼树写入文件TreePrint中。
2) 本程序包含七个模块
5. 主程序模块:
void main( ) {
初始化;
do {
接受命令;
处理命令;
} while (“命令”!=“退出”);
}
6. 初始化(Initialization)模块——从终端读入字符集大小n,以及n个字符和n个权值, .
建立哈夫曼树,并将它存于文件hfmTree中。
7. 编码(Encoding) 模块——利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
8. 译码(Decoding) 模块——利用已建好的哈夫曼树将文件CodeFile中的代码进行译
码,结果存入文件TextFile中。
印代码文件(Print) 模块——将文件CodeFile以紧凑格式显示在终端上,每行50个代
码。同时将此字符形式的编码文件写入文件CodePrin中。
9. 印哈夫曼树(Treeprinting) 模块——将已在内存中的哈夫曼树以直观的方式(树或凹
人表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
三、 详细设计
源代码:

//head.h
#ifndef __TEST_H__
#define __TEST_H__
#include<time.h>
#include<string.h>
#include<malloc.h> /* malloc()等 */
#include<limits.h> /* INT_MAX等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<stdlib.h> /* atoi() */
#include<math.h> /* floor(),ceil(),abs() */
#include<process.h> // exit() 

// 函数结果状态代码 
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
#include<dos.h>
typedef struct
{
    char c;
    unsigned int weight;
    unsigned int parent,lchild,rchild;
}HTNode,*HuffmanTree;   //动态分配数组存储哈夫曼树

typedef char** HuffmanCode;

typedef struct HuffmanChar
{
    unsigned int weight;
    char c;
}HuffmanChar;

void menu();
void Initialization();
void ntof(HuffmanTree HT,int n);
void scanfchar(HuffmanChar** hchararray,int n);
void HuffmanCoding(HuffmanTree* HT,HuffmanChar* hchararray,int n);
void Select(HuffmanTree HT,int n,int* s1,int* s2);
void Encoding();
void fton(HuffmanTree* HT,int* n);
void ttoa(HuffmanTree HT,int n,HuffmanCode *HC);
void Decoding();
void Print();
void Treeprinting();
void HuffmanTreeReverse(HuffmanTree HT,int posision,int blanknumber,FILE* pTreePrint,char flag);

#endif
#include"head.h"
#include"Initialization.h"
#include"Encoding.h"
#include"Decoding.h"
#include"Print.h"
#include"Treeprinting.h"

int main()
{
    //system("cls");            //清屏函数。
    char c;
    

    do{
        printf("请选择以下一个功能:\n");
        menu();
        c=getchar();
        system("cls");            //清屏函数。
        switch(c)
        {
            case'I':
                Initialization();
                system("pause");
                break;
            case'E':
                Encoding();
                system("pause");
                break;
            case'D':
                Decoding();
                system("pause");
                break;
            case'P':
                Print();
                system("pause");
                break;
            case'T':
                Treeprinting(); 
    			system("pause");
                break;
            case'Q':
                break;
            default:
                {
                    printf("输入错误!\n");
                    fflush(stdin);
                    system("pause");
                }
                
			fflush(stdin);
        }
        fflush(stdin);
        system("cls");            //清屏函数。
    }while(c!='Q');
    return 0;
}

void menu()
{
    printf("I:初始化(Initialization)\n");
    printf("E:编码(Encoding)\n");
    printf("D:译码(Decoding)\n"); 
    printf("P:印代码文件(Print)\n");
    printf("T:印哈夫曼树(Treeprinting)\n");
    printf("Q:退出(Quiting)\n");
    
}
// (5)T:印哈夫曼树(Treeprinting)。将已在内存中的哈夫曼树以直观的方式(树或凹
// 入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。




void Treeprinting()
{
    FILE* pTreePrint=fopen("C:/tmp/TreePrint.txt","w");
    int blanknumber=0;
    HuffmanTree HT;
    int n;
    fton(&HT,&n);
    int size=2*n-1;
    HuffmanTreeReverse(HT,size,blanknumber,pTreePrint,'M');
    fclose(pTreePrint);
    
}

void HuffmanTreeReverse(HuffmanTree HT,int posision,int blanknumber,FILE* pTreePrint,char flag)
{
    int i;
    if(posision==0) return;    
    for(i=0;i<blanknumber;i++)
    {
        putchar(' ');
        fputc(' ',pTreePrint);
        putchar(' ');
        fputc(' ',pTreePrint);
        putchar(' ');
        fputc(' ',pTreePrint);
        putchar(' ');
        fputc(' ',pTreePrint);
    }
    putchar(HT[posision].c);
    fputc(HT[posision].c,pTreePrint);
    printf("(%c)",flag);
    fputc('(',pTreePrint);
    fputc(flag,pTreePrint);
    fputc(')',pTreePrint);
    printf("---------------------------------------------------\n");
    fprintf(pTreePrint,"---------------------------------------------------\n");
    blanknumber++;
    HuffmanTreeReverse(HT,HT[posision].lchild,blanknumber,pTreePrint,'L');
    HuffmanTreeReverse(HT,HT[posision].rchild,blanknumber,pTreePrint,'R');

}

// (4)P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代
// 码。同时将此字符形式的编码文件写入文件CodePrin中。




void Print()
{   
    FILE* pCodeFile=fopen("C:/tmp/CodeFile.txt","r");
    FILE* pCodePrin=fopen("C:/tmp/CodePrin.txt","w");
    char buf[51];
    int count=0;
    while(fgets(buf,51,pCodeFile)!=NULL)
    {
        fprintf(pCodePrin,buf);
        fputc('\n',pCodePrin);
        printf("%s\n",buf);
    
        count++;
    }
    fseek(pCodeFile,50*count,SEEK_SET);
    char c=fgetc(pCodeFile);
    while(!feof(pCodeFile))
    {
        fputc(c,pCodePrin);
        putchar(c);
        c=fgetc(pCodeFile);
    }
    
    
    fclose(pCodeFile);
    fclose(pCodePrin);
    printf("打印完成。\n");
	 
}

//(1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值, .
//建立哈夫曼树,并将它存于文件hfmTree中。





void Initialization()
{
    HuffmanChar *hchararray;
    int n;
    printf("请输入待编码字符数目:\n");
    scanf("%d",&n);
    scanfchar(&hchararray,n);
    HuffmanTree HT;
    //HuffmanCode HC;//HC中字符顺序为hcharray中输入的顺序
    HuffmanCoding(&HT,hchararray,n);
    ntof(HT,n);//将哈夫曼树存储在文件中
    system("cls");
	 
}
void ntof(HuffmanTree HT,int n)
{
    int i;
    int flag=0;
    FILE *phfmTree=fopen("C:/tmp/hfmTree","wb");
 
    for(i=1;i<=2*n-1;i++)
    {
        if(fwrite(HT+i,sizeof(HTNode),1,phfmTree)==0) 
        {
        	printf("写入文件失败!");
        	flag=1;
        	break;
		}
	
		//fwrite(HT+i,sizeof(HTNode),1,phfmTree);
    }
    if(flag==0)	printf("写入文件成功!"); 
    system("pause");
    fclose(phfmTree);

}
void scanfchar(HuffmanChar** hchararray,int n)
{
	fflush(stdin);
    printf("请依次输入待编码字符及其权值,中间用逗号间隔,并按行分隔各组:\n");
    unsigned int i;
    *hchararray=(HuffmanChar*)malloc(sizeof(HuffmanChar)*n);
    for(i=0;i<n;i++)
    {
        (*hchararray)[i].c=getchar();
        getchar();//清理逗号
        scanf("%d",&(*hchararray)[i].weight);
        getchar();//清理回车换行号 
    }
}
void HuffmanCoding(HuffmanTree* HT,HuffmanChar* hchararray,int n)
{
    if(n<=1) return;
    int size=2*n-1;
    int i;
    *HT=(HuffmanTree)malloc((size+1)*sizeof(HTNode));//0号单元未用
    HuffmanTree p=(*HT)+1;
    for(i=1;i<=n;i++)//前n个单元存放树的叶子
    {
        p->c=hchararray->c;
        p->weight=hchararray->weight;
        p->parent=0;
        p->lchild=0;
        p->rchild=0;
        p++;
        hchararray++;
    }
    for(i=n+1;i<=size;i++)//后n-1个单元存放内部节点,其中最后一个节点为根节点
    {
        p->c='#';
        p->weight=0;
        p->parent=0;
        p->lchild=0;
        p->rchild=0;
        p++;
    }
    int s1,s2;

    for(i=n+1;i<=size;i++)//建哈夫曼树
    {//在(*HT)[1...i-1]选择parent为0且weight最小的两个结点,其序号分别为s1和s2,其中s1<s2
        Select((*HT),i-1,&s1,&s2);
        (*HT)[s1].parent=i;
        (*HT)[s2].parent=i;
        (*HT)[i].lchild=s1;
        (*HT)[i].rchild=s2;
        (*HT)[i].weight=(*HT)[s1].weight+(*HT)[s2].weight;
    }
    
}
void Select(HuffmanTree HT,int n,int* s1,int* s2)
{//在(*HT)[1...i-1]选择parent为0且weight最小的两个结点,其序号分别为s1和s2,其中s1<s2
    int i,flag=0;
    for(i=1;i<=n;i++)//求出前两个parent为零的i
    {
        if(HT[i].parent==0&&flag==0)  
        {
            *s1=i;
            flag=1;
        }
        else if(HT[i].parent==0&&flag==1)
        {
            *s2=i;
            break;
        } 
    }
    if(HT[*s1].weight>HT[*s2].weight)
    {
        flag=*s1;
        *s1=*s2;
        *s2=flag;
    }
    for(i++;i<=n;i++)
    {
        if(HT[i].parent==0&&HT[i].weight<HT[*s1].weight)
        {
            *s2=*s1;
            *s1=i;
        }
        else if(HT[i].parent==0&&HT[i].weight<HT[*s2].weight)
        {
            *s2=i;
        }
    }
}
//  E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中
// 读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。


void Encoding()
{
    HuffmanTree HT;
    int i,n;
    HuffmanCode HC;
    fton(&HT,&n);
    ttoa(HT,n,&HC);
    FILE* pCodeFile=fopen("C:/tmp/CodeFile.txt","w");
    FILE* pToBeTran=fopen("C:/tmp/ToBeTran.txt","r");
    char tbt=fgetc(pToBeTran);
    while(!feof(pToBeTran))
    {
        for(i=1;i<=n;i++)
        {
            if(tbt==HC[i][0])
            {
                fputs(HC[i]+1,pCodeFile );
                break;
            }   
        }
        tbt=fgetc(pToBeTran);
    }



    fclose(pCodeFile);
    fclose(pToBeTran);
    printf("\n编码完成\n");
}


void  fton(HuffmanTree* HT,int* n)
{
    int i,size;
    FILE* phfmTree =fopen("C:/tmp/hfmTree","rb");
    //查看文件大小
    fseek(phfmTree, 0, SEEK_END);  
    size = ftell(phfmTree)/sizeof(HTNode);  
    rewind(phfmTree);
    //创建文件大小的存储空间
    *HT=(HuffmanTree)malloc(sizeof(HTNode)*(size+1));
    fread(*HT+1,sizeof(HTNode),size,phfmTree);//录入完成
    
    fclose(phfmTree);
    *n=(size+1)/2;
}
void ttoa(HuffmanTree HT,int n,HuffmanCode *HC)
{
     //---从叶子到根逆向求每个字符的哈夫曼编码
    int i;
    char *cd=(char*)malloc(sizeof(char)*n);//为暂时存储分配字符编码的工作空间
    int current,currentparent;
    int currentcodeposision;
    *HC=(HuffmanCode)malloc(sizeof(char*)*(n+1));//分配n个字符的头指针向量,头指针的顺序与输入顺序一致,HC用来编码
    cd[n-1]='\0';//编码结束符
    for(i=1;i<=n;i++)//逐个字符求哈夫曼编码
    {
        currentcodeposision=n-1;//编码结束符位置,其开始位置由长度而定
        current=i;
        currentparent=HT[i].parent;
        while(currentparent!=0)//从叶子到根逆向求编码
        {
            if(HT[currentparent].lchild==current)    
            {
                currentcodeposision--;
                cd[currentcodeposision]='0'; 
            }
            else if(HT[currentparent].rchild==current)
            {
                currentcodeposision--;
                cd[currentcodeposision]='1';
            }
            current=currentparent;
            currentparent=HT[currentparent].parent;
        }
        currentcodeposision--;
        cd[currentcodeposision]=HT[i].c;
        (*HC)[i]=(char*)malloc(sizeof(char)*(n-currentcodeposision));
        strcpy((*HC)[i],cd+currentcodeposision);

    }
    free(cd);//释放工作空间
}


// (3)D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译
// 码,结果存入文件 TextFile 中


void Decoding()
{
    FILE* pCodeFile=fopen("C:/tmp/CodeFile.txt","r");
    FILE* pTextFile=fopen("C:/tmp/TextFile.txt","w");
    HuffmanTree HT;
    int n;
    fton(&HT,&n);
    int size=2*n-1;
    char currentchar=fgetc(pCodeFile);
    int curptr=size;
    while(!feof(pCodeFile))
    {
        if(currentchar=='0') curptr=HT[curptr].lchild;
        else curptr=HT[curptr].rchild;

        if(HT[curptr].lchild==0)
        {
            fputc(HT[curptr].c,pTextFile);
            putchar(HT[curptr].c);
            curptr=size;
        }

        currentchar=fgetc(pCodeFile);
    }


    fclose(pCodeFile);
    fclose(pTextFile);
    printf("\n译码完成\n");
}

函数调用关系图反映了演示程序的层次结构
在这里插入图片描述

四、 调试分析

  1. 出现频率高的字符用长码,概率小的用长码。由于哈夫曼树的wpl最小,这样,编码所需要的比特数最小,从而达到编码压缩的目的。
  2. 在编码过程中要考虑两个问题,第一,数据的最小冗余编码问题,第二,译码的唯一性问题
  3. 由于传送过程中没有出现分隔符,所以必须采用前缀编码,
    五、 用户手册
  4. 用户手册操作系统为win10,执行文件为main.exe
  5. 用户界面
    在这里插入图片描述
  6. 测试

I:录入上图的要求测试案例
Tobetran文件为:
SOME EIABHAFFMAN TEREE ELKAN EELSKDU XBEIOSN DISDJAELR
编码后Codefile文件为: 00111001110010010111010011010101000000001101011001111001111001010100111111110101000100100101110101011111000011101001111110100101011100111100001110110000011111100001010100000010011010010011011111110110011000111011011000010001010010101110010
P:打印Print:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200514170151916.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1c3R3Z3A=,size_16,color_FFFFFF,t_70
TextFile文件为:
SOME EIABHAFFMAN TEREE ELKAN EELSKDU XBEIOSN DISDJAELR
哈夫曼树的凹型表示为:
#(M)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
C(L)---------------------------------------------------
U®---------------------------------------------------
H®---------------------------------------------------
#®---------------------------------------------------
R(L)---------------------------------------------------
S®---------------------------------------------------
#®---------------------------------------------------
E(L)---------------------------------------------------
#®---------------------------------------------------
I(L)---------------------------------------------------
N®---------------------------------------------------
#®---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
B(L)---------------------------------------------------
G®---------------------------------------------------
#®---------------------------------------------------
P(L)---------------------------------------------------
Y®---------------------------------------------------
O®---------------------------------------------------
#®---------------------------------------------------
A(L)---------------------------------------------------
#®---------------------------------------------------
D(L)---------------------------------------------------
L®---------------------------------------------------
#®---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
V(L)---------------------------------------------------
#®---------------------------------------------------
#(L)---------------------------------------------------
#(L)---------------------------------------------------
J(L)---------------------------------------------------
Q®---------------------------------------------------
#®---------------------------------------------------
X(L)---------------------------------------------------
Z®---------------------------------------------------
K®---------------------------------------------------
W®---------------------------------------------------
#®---------------------------------------------------
M(L)---------------------------------------------------
F®---------------------------------------------------
T®---------------------------------------------------
®---------------------------------------------------

六、 附录

在这里插入图片描述

  • 9
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值