哈夫曼树编码、译码---(c语言实现)

1.上面是哈夫曼树?

1.1为什么要有哈夫曼树?

        先提出一个例子,加入现在有一串长度为8万的字符串,这些字符串全都由a、b、c、d、e和f,这六个字母组成。那么如何将这8万个字母组成的字符串转化为编码呢?最简易的编码方式肯定是将a编码000,b编码001,c编码010,d编码011,e编码110,f编码101,这样一来每个这整个字符串的长度就要占用3*80000=24万个比特位,对于这样的固定长度编码,每一个字符所占空间都是一样的,那么我们是否可以通过可变长度编码方式来实现节省空间呢?

        这就要提到我们的哈夫曼树编码了

1.2怎样进行哈夫曼编码呢?

        当我们开始对字符串进行可变长度编码时,我们需要统计出这些字符串中每个字母出现的次数,与之相对应的就是,出现次数最多的我们用长度最短的编码表示,出现次数最多的我们用可编码长度最长来表示,这样就可以实现我们的可编码长度。

        比如将上面的a、b、c、d、e和f分别出现3.2 1.6 0.8 0.7 0.6 1.1万次,我们将其编码为a=0 b=10 c=1110 d=10 e=1111 f=110现在我们再次计算他们的字符串长度:3.2*1+2*1.6+0.8*4+2*0.7+4*0.6+3*1.1=16.7。当我们这样编码的时候就已经少编码了7万多个比特位。(实际上这已经是最优情况了,下面会解释)

        为什么要按照上面的形式编码呢?其实编码的形式不唯一,但要遵守一个原则,没有一个编码是其他编码的前缀,为什么要遵守这个原则呢?

        加入我们将a的编码编码为1,那么ab组成的编码为110,当我们开始译码的时候,我们会发现,110即可译码为ab,也可以译码为f,所以这个时候就和我们的编码相冲突了,译码时有多选择,不唯一。但是当每一个字符的编码不是其他编码的前缀时,就不会出现这样的状况了,这时的编码长度就已经唯一了。

        以下是我们将我们构建的树进行可视化,(0表示左子树,1表示右子树)

        由图中可以看出,可变长度编码为 树为一颗满二叉树(当然形式不固定),而固定长度的编码却不是满二叉树,仅仅只是一颗普通的二叉树

        对于满二叉树,它的带权路径为最小,所以我们可以得出的编码数量和最小值。

        带权路径:从根到结点之间路劲的长度与结点上的权值的乘积

2.哈夫曼树代码

        先给出代码,下面会对代码进行解释。

        注:代码一为仅由权重而建立的哈夫曼树(本篇只解析了这个),代码二可以输入相应字符以及字符对应的权重而建树(这段代码和以上代码只做了部分修改,逻辑不变)

        注:以下代码是在CLion 2023.2.2语言环境中编辑的,若在其他环境中,可能会有警告,但都大同小异,改一下就好了。

2.1代码

2.1.1代码一 

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>

#define ElemType int

typedef struct Node{
    ElemType weight;
    int parent,left,right;
}HTNode,*HuffmanTree;   //哈夫曼树结构

typedef char **HuffmanCode; //动态内存分配huffman编码,二元数组指针

void Selection(HuffmanTree HT,int n,int* s1,int* s2);
void write_HuffmanTree(HuffmanTree HT,int n);  //将哈夫曼树以前序遍历的形式写入文件中
void Transform_Coding(HuffmanTree HT,int n);
void write_CodeFile(HuffmanCode HC,int n);
void HuffmanCoding(HuffmanTree HT,HuffmanCode HC,const int* w,int n);

int main(){
    int w[8]={5,29,7,8,14,23,3,11};  //测试值
    int n=8;
    HuffmanTree HT=NULL;
    HuffmanCode HC=NULL;
    HuffmanCoding(HT,HC,w,n);
    return 0;
}

void write_CodeFile(HuffmanCode HC,int n){
    FILE* file= fopen("CodeFile.txt","w");
    if(file==NULL){
        printf("%s\n", strerror(errno));  //若文件打开失败,打印出出问题的情况
        assert(file!=NULL);  //断言,进入这个循环则停止程序
    }
    int i;
    for(i=1;i<=n;i++){
        fprintf(file,"%s ",HC[i]);
    }
    fclose(file);
    file=NULL;
}

//将哈夫曼树以前序遍历的形式写入文件中
void write_HuffmanTree(HuffmanTree HT,int n){
    FILE* file= fopen("hfmTree.txt","w");
    if(file==NULL){
        printf("%s\n", strerror(errno));
        return;
    }
    //将HC中的写入文件中
    int i;
    fprintf(file,"%s\t%s\t%s\t%s\n","weight","parent","left","right");
    for(i=1;i<=2*n-1;i++){
        fprintf(file, "%-6d\t%-6d\t%-4d\t%-5d\n",HT[i].weight,HT[i].parent,HT[i].left,HT[i].right);
    }
    fclose(file);
    file=NULL;
}

void Selection(HuffmanTree HT,int n,int* s1,int* s2){
    //找到HT[1...i-1]中parent为0且weight最小的两个结点
    int i;
    int a1,a2;    //用a来记住最小权重的位置
    a1=a2=-1;
    for(i=0;i<=n;i++){
        if(HT[i].parent==0){
            if(a1==-1||HT[i].weight<HT[a1].weight){   //只需要让a2记住上一个a1的位置即可
                a2=a1;
                a1=i;
            } else if(a2==-1||HT[i].weight<HT[a2].weight){
                a2=i;
            }
        }
    }
    *s1=a1;
    *s2=a2;
}

void HuffmanCoding(HuffmanTree HT,HuffmanCode HC,const int* w,int n){  //w存放n个字符的权值,n即字符数
    if(n<=1){
        printf("have not found the chars\n");
        return;
    }
    int m=2*n-1;   //哈夫曼树中的总结点个数
    HT=(HuffmanTree) malloc((m+2)* sizeof(HTNode));  //0号单元未用所以加一,(但实际上我加了二)
    if(HT==NULL){
        printf("%s\n", strerror(errno));
        return;
    }
    int i;
    HuffmanTree p;
    for (p=HT+1,i=1;i<=m;++p,++i) {    //哈夫曼数组初始化
        p->right=0;
        p->left=0;
        p->weight=0;
        p->parent=0;
    }
    for(p=HT+1,i=1;i<=n;++p,++i,++w){   //初始化权重
        p->weight=*w;
    }
    //写一个权重挑选函数
    for(i=n+1;i<=m+1;++i){
        int S1,S2;
        Selection(HT,i-1,&S1,&S2);     //找到两个最小的结点,并不能找到对应的最小值的位置
        HT[i].weight=HT[S1].weight+HT[S2].weight;//此结点的权重为S1+S2的权重
        HT[S1].parent=HT[S2].parent=i;   //更新最小两个结点的双亲结点
        HT[i].left=S1;
        HT[i].right=S2;   //更新孩子结点
    }
    //需要对编写的哈夫曼树进行写入文件

    HT[15].parent=0;  //将根节点的父亲结点,设置为0,便于之后进行编码的时候搜索到根节点,不至于报错Segmentation fault

    write_HuffmanTree(HT,n);  //写如文件操作

    HC=(HuffmanCode) malloc((n+1)*sizeof (char*));   //给二维数组指针进行分配空间
    if(HC==NULL)printf("%s\n", strerror(errno));  //判断是否分配失败
    assert(HC!=NULL);   //断言!阻止程序进行运行下去

    char* coding=(char*) malloc(n*sizeof (char));   //分配编码空间
    if(coding==NULL)printf("%s\n", strerror(errno));  //判断是否分配失败
    assert(coding!=NULL);  //阻止程序进行运行下去

    //从叶子结点到根逆向求解出每个字符的HUFFMAN编码
    coding[n-1]='\0';  //最后一个字符为编码结束符号

    for(i=1;i<=n;i++){
        int start=n-1;  //用于coding[]的动态存储
        int thread;
        int current;
        //当i=15的时候,并不能直接找到15的结点,当i等于15的时候直接跳出这一次到下一次循环
        if (i > n) {
            continue;
        }
        for(current=i,thread=HT[i].parent;thread!=0;current=thread,thread=HT[thread].parent){
            if(HT[thread].left==current){    //若是左孩子,则编码为0
                coding[--start]='0';
            } else{
                coding[--start]='1';
            }
        }
        HC[i]=(char*) malloc((n-start)*sizeof (char)); //为第i个字符编码分配空间,刚好可以分配出需要的编码空间
        strcpy(HC[i],&coding[start]);  //将字符串拷贝
    }

    write_CodeFile(HC,n);  //编码进去

    Transform_Coding(HT,n);

    free(coding);  //释放原来的空间
    free(HC);
    free(HT);
    HC=NULL;
    HT=NULL;
}   //创建哈夫曼树,并且编码哈夫曼树

//编辑译码函数,针对上面的HC[],我们需要将编码与HC[]对应起来,看是第几个,找出其中的i,然后打印对应的HT[i]
//涉及到子串的问题
void Transform_Coding(HuffmanTree HT,int n){
    FILE* pf_read= fopen("CodeFile.txt","r");
    FILE* pf_write= fopen("TextFile.txt","w");
    if(pf_read==NULL||pf_write==NULL){
        printf("%s\n", strerror(errno));
        assert(pf_write!=NULL);
        assert(pf_read!=NULL);
    }
    char buffer[100];  //存储从文件读取的二进制码
    fgets(buffer, sizeof(buffer),pf_read);//读到文件
    int current=2*n-1;  //直接指向根结点
    int i=0;
    while (buffer[i]!='\0'){  //直到当前二进制字符串结束,跳出循环,译码结束
        if(buffer[i]=='0'){
            current=HT[current].left;  //左子树移动,根据之前编码的情况进行选择
        } else if(buffer[i]=='1'){
            current=HT[current].right;  //右子树移动
        }
        if(HT[current].left==0&&HT[current].right==0){   //孩子结点都为0.说明已经走到叶子结点
            fprintf(pf_write, "%d ", HT[current].weight);
            current=2*n-1;   //重置根结点,以便继续解码
        }
        i++;
    }
    fclose(pf_read);  //关闭文件
    fclose(pf_write);
    pf_write=NULL;  //置为NULL
    pf_read=NULL;
}

 2.1.2代码二

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>

#define ElemType char

typedef struct Node{
    ElemType character;
    int weight;
    int parent,left,right;
}HTNode,*HuffmanTree;   //哈夫曼树结构

typedef char **HuffmanCode; //动态内存分配huffman编码,二元数组指针

void Selection(HuffmanTree HT,int n,int* s1,int* s2);
void write_HuffmanTree(HuffmanTree HT,int n);  //将哈夫曼树以前序遍历的形式写入文件中
void Transform_Coding(HuffmanTree HT,int n);
void write_CodeFile(HuffmanCode HC,int n);
void HuffmanCoding(HuffmanTree HT,HuffmanCode HC,const HTNode* w,int n);
void menu();
//输入一系列的字符串,以及对应的权重
//编码还是按照原始来编码,那么在写入文件的时候,就是写入权值

int main(){
    int capacity=3;  //默认分配的数组空间
    int size=0;  //当前已用的数组空间
    HTNode* w = (HTNode*)malloc(capacity * sizeof(HTNode));  //分配默认空间
    if (w == NULL) {
        printf("%s", strerror(errno));
        exit(1);
    }
    int i=0;
    do {
        menu();
        scanf_s(" %c %d", &w[i].character, sizeof(w[i].character), &w[i].weight); //输入字符以及对应的权值
        size += 1;  //计数
        if (size == capacity) {  //如果数组空间不够,扩充一倍
            capacity *= 2;
            HTNode *temp = (HTNode*)realloc(w, capacity * sizeof(HTNode));
            if (temp == NULL) { // 如果重新分配失败,则直接退出程序
                printf("%s", strerror(errno));
                free(w); //释放之前已分配的内存
                exit(1);
            }
            w = temp;
        }

        i+=1;
    } while (w[i-1].character != '-');
    int n=size-1;
    HuffmanTree HT=NULL;
    HuffmanCode HC=NULL;
    HuffmanCoding(HT,HC,w,n);
    return 0;
}

void menu(){
    printf("Please input the char and weight(enter -1 to leave):");
}

void write_CodeFile(HuffmanCode HC,int n){
    FILE* file= fopen("CodeFile.txt","w");
    if(file==NULL){
        printf("%s\n", strerror(errno));  //若文件打开失败,打印出出问题的情况
        assert(file!=NULL);  //断言,进入这个循环则停止程序
    }
    int i;
    for(i=1;i<=n;i++){
        fprintf(file,"%s ",HC[i]);
    }
    fclose(file);
    file=NULL;
}

//将哈夫曼树以前序遍历的形式写入文件中
void write_HuffmanTree(HuffmanTree HT,int n){
    FILE* file= fopen("hfmTree.txt","w");
    if(file==NULL){
        printf("%s\n", strerror(errno));
        return;
    }
    //将HC中的写入文件中
    int i;
    fprintf(file,"%s\t%s\t%s\t%s\t%s\n","char","weight","parent","left","right");
    for(i=1;i<=2*n-1;i++){
        fprintf(file, "%-4c\t%-6d\t%-6d\t%-4d\t%-5d\n",HT[i].character,HT[i].weight,HT[i].parent,HT[i].left,HT[i].right);
    }
    fclose(file);
    file=NULL;
}

void Selection(HuffmanTree HT,int n,int* s1,int* s2){
    //找到HT[1...i-1]中parent为0且weight最小的两个结点
    int i;
    int a1,a2;    //用a来记住最小权重的位置
    a1=a2=-1;
    for(i=0;i<=n;i++){
        if(HT[i].parent==0){
            if(a1==-1||HT[i].weight<HT[a1].weight){   //只需要让a2记住上一个a1的位置即可
                a2=a1;
                a1=i;
            } else if(a2==-1||HT[i].weight<HT[a2].weight){
                a2=i;
            }
        }
    }
    *s1=a1;
    *s2=a2;
}

void HuffmanCoding(HuffmanTree HT,HuffmanCode HC,const HTNode* w,int n){  //w存放n个字符的权值,n即字符数
    if(n<=1){
        printf("have not found the chars\n");
        return;
    }
    int m=2*n-1;   //哈夫曼树中的总结点个数
    HT=(HuffmanTree) malloc((m+1)* sizeof(HTNode));  //0号单元未用所以加一,(但实际上我加了二)
    if(HT==NULL){
        printf("%s\n", strerror(errno));
        return;
    }
    int i;
    HuffmanTree p;
    for (p=HT+1,i=1;i<=m;++p,++i) {    //哈夫曼数组初始化
        p->right=0;
        p->left=0;
        p->weight=0;
        p->parent=0;
        p->character='0';
    }
    for(p=HT+1,i=1;i<=n;++p,++i,++w){   //初始化权重
        p->weight=w->weight;
        p->character=w->character;
    }
    //写一个权重挑选函数
    for(i=n+1;i<=m;++i){
        int S1,S2;
        Selection(HT,i-1,&S1,&S2);     //找到两个最小的结点,并不能找到对应的最小值的位置
        HT[i].weight=HT[S1].weight+HT[S2].weight;//此结点的权重为S1+S2的权重
        HT[S1].parent=HT[S2].parent=i;   //更新最小两个结点的双亲结点
        HT[i].character='#';
        HT[i].left=S1;
        HT[i].right=S2;   //更新孩子结点
    }
    //需要对编写的哈夫曼树进行写入文件

    write_HuffmanTree(HT,n);  //写如文件操作

    HC=(HuffmanCode) malloc((n+1)*sizeof (char*));   //给二维数组指针进行分配空间
    if(HC==NULL)printf("%s\n", strerror(errno));  //判断是否分配失败
    assert(HC!=NULL);   //断言!阻止程序进行运行下去

    char* coding=(char*) malloc(n*sizeof (char));   //分配编码空间
    if(coding==NULL)printf("%s\n", strerror(errno));  //判断是否分配失败
    assert(coding!=NULL);  //阻止程序进行运行下去

    //从叶子结点到根逆向求解出每个字符的HUFFMAN编码
    coding[n-1]='\0';  //最后一个字符为编码结束符号

    for(i=1;i<=n;i++){
        int start=n-1;  //用于coding[]的动态存储
        int thread;
        int current;
        //当i=15的时候,并不能直接找到15的结点,当i等于15的时候直接跳出这一次到下一次循环
        if (i > n) {
            continue;
        }
        for(current=i,thread=HT[i].parent;thread!=0;current=thread,thread=HT[thread].parent){
            if(HT[thread].left==current){    //若是左孩子,则编码为0
                coding[--start]='0';
            } else{
                coding[--start]='1';
            }
        }
        HC[i]=(char*) malloc((n-start)*sizeof (char)); //为第i个字符编码分配空间,刚好可以分配出需要的编码空间
        strcpy(HC[i],&coding[start]);  //将字符串拷贝
    }

    write_CodeFile(HC,n);  //编码进去

    Transform_Coding(HT,n);

    free(coding);  //释放原来的空间
    free(HC);
    HC=NULL;
    HT=NULL;
}   //创建哈夫曼树,并且编码哈夫曼树

//编辑译码函数,针对上面的HC[],我们需要将编码与HC[]对应起来,看是第几个,找出其中的i,然后打印对应的HT[i]
//涉及到子串的问题
void Transform_Coding(HuffmanTree HT,int n){
    FILE* pf_read= fopen("CodeFile.txt","r");
    FILE* pf_write= fopen("TextFile.txt","w");
    if(pf_read==NULL||pf_write==NULL){
        printf("%s\n", strerror(errno));
        assert(pf_write!=NULL);
        assert(pf_read!=NULL);
    }
    char buffer[100];  //存储从文件读取的二进制码
    fgets(buffer, sizeof(buffer),pf_read);//读到文件
    int current=2*n-1;  //直接指向根结点
    int i=0;
    while (buffer[i]!='\0'){  //直到当前二进制字符串结束,跳出循环,译码结束
        if(buffer[i]=='0'){
            current=HT[current].left;  //左子树移动,根据之前编码的情况进行选择
        } else if(buffer[i]=='1'){
            current=HT[current].right;  //右子树移动
        }
        if(HT[current].left==0&&HT[current].right==0){   //孩子结点都为0.说明已经走到叶子结点
            fprintf(pf_write, "%c ", HT[current].character);
            current=2*n-1;   //重置根结点,以便继续解码
        }
        i++;
    }
    fclose(pf_read);  //关闭文件
    fclose(pf_write);
    pf_write=NULL;  //置为NULL
    pf_read=NULL;
}

2.2代码详解 

        我们先从抽象数据结构类型开始讲解: 

        我们将哈夫曼树抽象为整形存储结构,对于双亲结点和孩子结点,我们都采用整形存储类型。原因是我们将其用二维数组的存储方式进行存储。对于二维数组的每行的每个元素依次表示这行的权重、双亲结点、左孩子和右孩子。如下:

哈夫曼树的存储形式
HuffmanCodeweightparentleftchildrightchild

        对于HuffmanCode之后会做解释 。

        现在解释对于哈夫曼树编码的代码:

        先提前对总结点做计算,通过满二叉树的性质,满二叉树结点的总数等于叶子结点乘二减一,所以我们在对哈夫曼树进行建树前,需要先分配内存空间(注:并非分配2*n-1个结点,我们分配2*n个结点,0号结点我们不使用)(但我在实现代码的时候分配2*n个空间会报栈溢出的问题,所以我分配了2*n+1个结点,当然,欢迎读者尝试),如下图:

        然后就是对我们分配的内存空间进行初始化,将每个结点的权值、双亲节点、孩子结点全都赋值为0,然后在将对应权值按顺序分配给不同的结点:

         既然权重已经对应分配给各个结点了,那么我们可以开始建树了,先给出我们建树模型,按照下列二维表格表征我们的二维数组哈夫曼树

序号weightparentleftchildrightchild
15900
2291400
371000
481000
5141200
6231300
73900
8111100
98        1117
10151234
11191389
122914510
134215611
145815212
1510001314

        以及形象的哈夫曼树(哈夫曼树有多种形式,但都是满二叉树)

        如上图所示,就是我们建立的哈夫曼树。上图就是通过我们循环找到最小两个结点,然后将最小两个结点的权重相加,生成一个新的结点,新结点的左孩子右孩子结点就为刚刚找到的最小的两个结点:

       其中还有一个重要的点就是我们的Selection函数,我们需要通过Selection函数来找到当前所有结点的最小两个值的位置。我们需要将当前的哈夫曼树传入HT、结点数、以及S1与S2的地址。先建立变量用于存储找到的最下位置的值,先将这两个存储变量赋值为-1,然后通过循环来存储对应的位置,a2置于a1前,用来存储前一个的最小值,这样就可以记录下最小的两个值。

        至于为什么 i 要从n+1开始循环,因为n以及小于n的数在最开始初始化的时候就已经分配了权值了,所以我们从第n+1个开始,记录我们新生成的结点,以此循环,直到将所有结点都连接起来生成我们的哈夫曼树。但是,注意:因为我们的循环要直到m+1,第十六也要循环,所以会将第15个结点(根节点)的双亲结点也给赋值为 i (16);所以我们需要将其双亲结点赋值为0,因为根节点没有双亲结点:

   

         现在将哈夫曼树建立成功之后,我们可以将哈夫曼树写入我们的文件中:

      

        通过写入文件之后,就可以在编码的过程中检查文件中的哈夫曼树判断建树是否成功。

        既然建树结束了,那么现在就该到我们的编码步骤了。先解释编码原理:通过将循环,对前n个结点都访问一遍(因为前n个结点为最初的n个结点,编码只需要对最初的编码即可),然后通过循环对结点进行编码:找到当前结点的双亲结点,若双亲结点不为0,则继续进行编译。若当前结点的双亲结点为当前结点的左孩子(简单来说:目前遍历的结点为它双亲结点的左孩子)编码为0,若为右孩子编码为1,直到双亲结点为0(根节点的双亲结点为0)停止这一结点的遍历。

        然后将以及编好码的数,存储到文件中(通过简单的循环就可),便于后期可以从文件中读取之后,将其译码:

        既然编码的情况已经讲完,那么现在可以到译码的阶段了: 

        首先需要读取到我们的文件,将刚刚读取的文件中的编码存储到我们的一个数组中,需要从根节点开始寻找叶子结点(一开始用current存储根节点),若当前二进制码为0,则将current赋值为它的左孩子结点,反之,右孩子结点。当当前结点的孩子结点都为0时,表明已经到达叶子结点,将这个结点的权值打印到新的译码文件中。以此循环,直到二进制串译码结束:

        最后给出我们main函数,其中w数组为测试数据: 

         

 2.3代码测试

        对于哈夫曼树的建立:

        对于编码的情况: 

        对于译码的情况:

        由上图所示,我们所测试的情况译码和测试值一样,说明我们的代码可以解决编码译码的问题。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                                                                       分割线

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        欢迎评论区的各位堡子提出意见和指出错误^_^,如果觉得有用就点个赞吧

         

  • 5
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#include #include #include #include using namespace std; # define MaxN 100//初始设定的最大结点数 # define MaxC 1000//最大编码长度 # define ImpossibleWeight 10000//结点不可能达到的权值 # define n 26//字符集的个数 //-----------哈夫曼树的结点结构类型定义----------- typedef struct //定义哈夫曼树各结点 { int weight;//权值 int parent;//双亲结点下标 int lchild;//左孩子结点下标 int rchild;//右孩子结点下标 }HTNode,*HuffmanTree;//动态分配数组存储哈夫曼树 typedef char**HuffmanCode;//动态分配数组存储哈夫曼编码表 //-------全局变量-------- HuffmanTree HT; HuffmanCode HC; int *w;//权值数组 //const int n=26;//字符集的个数 char *info;//字符值数组 int flag=0;//初始化标记 //********************************************************************** //初始化函数 //函数功能: 从终端读入字符集大小n , 以及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmTree中 //函数参数: //向量HT的前n个分量表示叶子结点,最后一个分量表示根结点,各字符的编码长度不等,所以按实际长度动态分配空间 void Select(HuffmanTree t,int i,int &s1,int &s2) { //s1为最小的两个值中序号最小的那个 int j; int k=ImpossibleWeight;//k的初值为不可能达到的最大权值 for(j=1;j<=i;j++) { if(t[j].weight<k&&t[j].parent==0) {k=t[j].weight; s1=j;} } t[s1].parent=1; k=ImpossibleWeight; for(j=1;j<=i;j++) { if(t[j].weight0),构造哈夫曼树HT,并求出n个字符的哈弗曼编码HC { int i,m,c,s1,s2,start,f; HuffmanTree p; char* cd; if(num<=1) return; m=2*num-1;//m为结点数,一棵有n个叶子结点的哈夫曼树共有2n-1个结点,可以存储在一个大小为2n-1的一维数组中 HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0号单元未用 //--------初始化哈弗曼树------- for(p=HT+1,i=1;iweight=*w; p->parent=0; p->lchild=0; p->rchild=0; } for(i=num+1;iweight=0; p->parent=0; p->lchild=0; p->rchild=0; } //--------建哈夫曼树------------- for(i=num+1;i<=m;i++) { Select(HT,i-1,s1,s2);//在HT[1...i-1]选择parent为0且weight最小的两个结点,其序号分别为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; } //-------从叶子到根逆向求每个字符的哈弗曼编码-------- HC=(HuffmanCode)malloc((num+1)*sizeof(char *));//指针数组:分配n个字符编码的头指针向量 cd=(char*)malloc(n*sizeof(char*));//分配求编码的工作空间 cd[n-1]='\0';//编码结束符 for(i=1;i<=n;i++)//逐个字符求哈弗曼编码 { start=n-1;//编码结束符位置 for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)//从叶子到跟逆向求哈弗曼编码 if(HT[f].lchild==c) cd[--start]='0';//判断是左孩子还是右孩子(左为0右为1) else cd[--start]='1'; HC[i]=(char*)malloc((num-start)*sizeof(char*));//按所需长度分配空间 int j,h; strcpy(HC[i],&cd[start]); } free(cd); } //****************初始化函数****************** void Initialization() { flag=1;//标记为已初始化 int i; w=(int*)malloc(n*sizeof(int));//为26个字符权值分配空间 info=(char*)malloc(n*sizeof(char));//为26个字符分配空间 ifstream infile("ABC.txt",ios::in); if(!infile) { cerr<<"打开失败"<<endl; exit(1); } for(i=0;i>info[i]; infile>>w[i]; } infile.close(); cout<<"读入字符成功!"<<endl; HuffmanCoding(HT,HC,w,n); //------------打印编码----------- cout<<"依次显示各个字符的值,权值或频度,编码如下"<<endl; cout<<"字符"<<setw(6)<<"权值"<<setw(11)<<"编码"<<endl; for(i=0;i<n;i++) { cout<<setw(3)<<info[i]; cout<<setw(6)<<w[i]<<setw(12)<<HC[i+1]<<endl; } //---------将建好的哈夫曼树写入文件------------ cout<<"下面将哈夫曼树写入文件"<<endl; ofstream outfile("hfmTree.txt",ios::out); if(!outfile) { cerr<<"打开失败"<<endl; exit(1); } for(i=0;i<n;i++,w++) { outfile<<info[i]<<" "; outfile<<w[i]<<" "; outfile<<HC[i+1]<<" "; } outfile.close(); cout<<"已经将字符与对应的权值,编码写入根目录下文件hfmTree.txt"<<endl; } //*****************输入待编码字符函数************************* void Input() { char string[100]; ofstream outfile("ToBeTran.txt",ios::out); if(!outfile) { cerr<<"打开失败"<<endl; exit(1); } cout<<"请输入你想要编码字符串(字符个数应小于100),以#结束"<>string; for(int i=0;string[i]!='\0';i++) { if(string[i]=='\0') break; outfile<<string[i]; } cout<<"获取报文成功"<<endl; outfile.close(); cout<<"------"<<"已经将报文存入根目录下的ToBeTran.txt文件"<<endl; } //******************编码函数**************** void Encoding() { int i,j; char*string; string=(char*)malloc(MaxN*sizeof(char)); cout<<"下面对根目录下的ToBeTran.txt文件中的字符进行编码"<<endl; ifstream infile("ToBeTran.txt",ios::in); if(!infile) { cerr<<"打开失败"<<endl; exit(1); } for(i=0;i>string[i]; } for(i=0;i<100;i++) if(string[i]!='#') cout<<string[i]; else break; infile.close(); ofstream outfile("CodeFile.txt",ios::out); if(!outfile) { cerr<<"打开失败"<<endl; exit(1); } for(i=0;string[i]!='#';i++) { for(j=0;j<n;j++) { if(string[i]==info[j]) outfile<<HC[j+1]; } } outfile<<'#'; outfile.close(); free(string); cout<<"编码完成------"; cout<<"编码已写入根目录下的文件CodeFile.txt中"<<endl; } //******************译码函数**************** void Decoding() { int j=0,i; char *code; code=(char*)malloc(MaxC*sizeof(char)); char*string; string=(char*)malloc(MaxN*sizeof(char)); cout<<"下面对根目录下的CodeFile.txt文件中的代码进行译码"<<endl; ifstream infile("CodeFile.txt",ios::in); if(!infile) { cerr<<"打开失败"<<endl; exit(1); } for( i=0;i>code[i]; if(code[i]!='#') { cout<<code[i]; } else break; } infile.close(); int m=2*n-1; for(i=0;code[i-1]!='#';i++) { if(HT[m].lchild==0) { string[j]=info[m-1]; j++; m=2*n-1; i--; } else if(code[i]=='1') m=HT[m].rchild; else if(code[i]=='0') m=HT[m].lchild; } string[j]='#'; ofstream outfile("TextFile.txt",ios::out); if(!outfile) { cerr<<"打开失败"<<endl; exit(1); } cout<<"的译码------"<<endl; for( i=0;string[i]!='#';i++) { outfile<<string[i]; cout<<string[i]; } outfile<<'#'; outfile.close(); cout<<"------译码完成------"<<endl; cout<<"译码结果已写入根目录下的文件TextFile.txt中"<<endl; free(code); free(string); } //*************打印编码函数**************** void Code_printing() { int i; char *code; code=(char*)malloc(MaxC*sizeof(char)); cout<<"下面打印根目录下文件CodeFile.txt中的编码"<<endl; ifstream infile("CodeFile.txt",ios::in); if(!infile) { cerr<<"打开失败"<<endl; exit(1); } for( i=0;i>code[i]; if(code[i]!='#') cout<<code[i]; else break; } infile.close(); cout<<endl; ofstream outfile("CodePrin.txt",ios::out); if(!outfile) { cerr<<"打开失败"<<endl; exit(1); } for(i=0;code[i]!='#';i++) { outfile<<code[i]; } outfile.close(); free(code); cout<<"------打印结束------"<<endl; cout<<"该字符形式的编码文件已写入文件CodePrin.txt中"<<endl; } //*************打印哈夫曼树函数**************** int numb=0; void coprint(HuffmanTree start,HuffmanTree HT) //start=ht+26这是一个递归算法 { if(start!=HT) { ofstream outfile("TreePrint.txt",ios::out); if(!outfile) { cerr<<"打开失败"<rchild,HT); //递归先序遍历 cout<<setw(5*numb)<weight<rchild==0) cout<<info[start-HT-1]<<endl; outfile<weight; coprint(HT+start->lchild,HT); numb--; outfile.close(); } } void Tree_printing(HuffmanTree HT,int num) { HuffmanTree p; p=HT+2*num-1; //p=HT+26 cout<<"下面打印赫夫曼树"<<endl; coprint(p,HT); //p=HT+26 cout<<"打印工作结束"<<endl; } //*************主函数************************** int main() { char choice; do{ cout<<"************哈弗曼编/译码器系统***************"<<endl; cout<<"请选择您所需功能:"<<endl; cout<<":初始化哈弗曼树"<<endl; cout<<":输入待编码字符串"<<endl; cout<<":利用已建好的哈夫曼树进行编码"<<endl; cout<<":利用已建好的哈夫曼树进行译码"<<endl; cout<<":打印代码文件"<<endl; cout<<":打印哈夫曼树"<<endl; cout<<":退出"<<endl; if(flag==0) { cout<<"请先初始化哈夫曼树,输入I"<<endl; cout<<""<>choice; switch(choice) { case 'I':Initialization();break; case 'W':Input();break; case 'E':Encoding();break; case 'D':Decoding();break; case 'P':Code_printing();break; case 'T':Tree_printing(HT,n);break; case 'Q':;break; default:cout<<"输入的命令出错,请重新输入!"<<endl; } }while(choice!='Q'); free(w); free(info); free(HT); free(HC); system("pause"); return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值