哈夫曼树与哈夫曼编码

哈夫曼树与哈夫曼编码



前言

实训要求哈夫曼编码和译码,写得不是很好,刚开始有很多bug,在老师同学的热心帮助下完成了本次实训,还有诸多不理解的地方,在此记录共同分享


提示:以下是本篇文章正文内容,下面案例可供参考

一、哈夫曼编码:

对于一棵具有n个叶子的哈夫曼树,若对树中的每个左分支赋予0,右分支赋予1,则从根到每个叶子的路径上,各分支的赋值分别构成一个二进制串,该二进制串就称为哈夫曼编码

二、实训要求

哈夫曼编码和译码
 输入为:一段英文为或中文的文章
 对输入的文章构造哈夫曼树
 生成对应的编码
 输出为:原文所对应的编码(译文)
 根据已经生成的编码表,输入任意的译文可以得到对应的原文

三、系统功能

(1) 初始化输入文段中的各个字母,统计各个字母及其个数作为权值
(2) 根据输入的字母和统计后的权值构造哈夫曼树
(3) 从哈夫曼树叶子结点到根结点逆向求每个字符的哈夫曼编码
(4) 对编码后的字母,能用二进制译码翻译出字母

四、实训步骤

#include
#include
#include 
#include
#include
#define MAX_MA 1000
#define MAX_ZF 100
#define MAX_LENGTH 26
using namespace std;

//哈夫曼树的顺序储存表示
typedef struct {
    int weight;  //结点的权值
    char info;
    int parent,lchild,rchild;  //结点的双亲,左孩子,右孩子的下标
} HTNode,*HuffmanTree;  //动态分配数组来储存哈夫曼树的结点
typedef struct
{
    char data;
    int count;
} numCount; //结构体类型的字母数组,包含字母信息和出现次数
//哈夫曼编码表的储存表示
typedef char **HuffmanCode;  //动态分配数组来储存哈夫曼编码


int zfInit(numCount *nums)
{
    int i;
    for(i=1; i<=MAX_LENGTH; i++)    //英文字母数组初始化
    {
        nums[i].count=0;
        nums[i].data=i+97-1;

    }
	return nums[i].count;
}


int min_index(HuffmanTree &HT,int len){
	int i,min;
	int minweight = 32767;
	
	for( i = 1;i<=len; i++){
		
		if(HT[i].parent == 0 && HT[i].weight<minweight && HT[i].weight>0){
			min = i;
			minweight = HT[i].weight;
			 
			
		}		
	}
	//找到最小值的下标后 让它的双亲等于它的新的节点 
	HT[min].parent =  len+1;
 	return min;
}

int select(HuffmanTree HT,int len,int &s1,int &s2){
	//返回第一个最小值 
	s1 = min_index(HT,len);
	//返回第二个最小值 
	s2 = min_index(HT,len);
return 1;
	 
}


```c
//构造哈夫曼树HT
int CreatHuffmanTree(HuffmanTree &HT,int n,numCount *numc) {
	
    int s1,s2,i,m,k = 0,count = 0;

    if(n<=1)
        return 0;
    m=2*n-1;  //当n大于1时,n个叶子结点需要2*n-1个结点
    HT=new HTNode[m+1];  //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点
    //将1~m号单元中的双亲、左孩子,右孩子的下标都初始化为0

    for(i=1; i<=m; ++i) {
        HT[i].parent=0;
        HT[i].lchild=0;
        HT[i].rchild=0;
		HT[i].weight=0;
		
    }
    for(i=1;i<=n;++i){
    	//这里的意思是 如果count>0 也就是说 字母出现的次数大于0 就赋值到HT里面 其他count<0就不赋值 
    	if(numc[i].count>0){
    	k++;
		HT[k].weight = numc[i].count;
		HT[k].info = numc[i].data;
				
		}
		
	}	
	//最大生成节点数 
	m = 2*k-1;
	printf(" m = %d",m);
   //从1开始选择、合并来创建哈夫曼树
    for(i=k+1; i<=m; ++i) {
        select(HT,i-1,s1,s2);  
        //s1,s2分别作为i的左右孩子
        HT[i].lchild=s1;
        HT[i].rchild=s2;
        //i的权值为左右孩子权值之和
        HT[i].weight=HT[s1].weight+HT[s2].weight;
        
        HT[i].info=' ';

    }
    //把k返回出去 
    return k;
}
//哈夫曼编码
void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int n) {
	char *cd;
	int i,c,f,start;
    //从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
    HC=new char *[n+1];  //分配n个字符编码的编码表空间(头指针矢量)
    cd=new char[n];  //分配临时存放编码的动态数组空间
    cd[n-1]='\0';  //编码结束符
    //从叶子开始逐个字符求哈夫曼编码
    for( i=1; i<=n; ++i) {
         start=n-1;  //start开始时指向最后,即编码结束符位置
         c=i;
         f=HT[i].parent;  //f是指向结点c的双亲结点
        //从叶子结点开始向上回溯,直到根结点
        while(f!=0) {
            --start;  //回溯一次,start向前指向一个位置
            //判断是结点c是f的左孩子还是右孩子
            if(HT[f].lchild==c) {
                cd[start]='0';  //结点c是f的左孩子,则生成代码0
            } else {
                cd[start]='1';  //结点c是f的右孩子,则生成代码1
            }
            c=f;
            f=HT[f].parent;  //继续向上回溯
        }  //求出第i个字符的编码
        HC[i]=new char[n-start];  //为第i个字符编码分配空间
        strcpy(HC[i],&cd[start]);  //将求得的编码从临时空间cd复制到HC的当前行中
    }
    delete cd;  //释放临时空间
}


//键盘输入解码
void HafumanTreeDecode(	HuffmanTree HT,char *Decode,int max_node){
	 
	int i,k=0;
    char c;
    scanf("%c", &c);
	i = max_node;//根结点的小标(地址)为2*N-2
	 
	while (c!='#')//#结束后不再翻译
    {	 
    	
        	if (c == '0'){
        	//‘0’判断左走
            	i = HT[i].lchild;

        	}else if (c == '1'){
        	//‘1’判断右走
            	i = HT[i].rchild;
            	
			}
			
        	if (HT[i].lchild == 0 || HT[i].rchild == 0)//从根结点一直找到叶子
        	{
        		
            	Decode[k++] = HT[i].info;
            	i = max_node;//译完一段编码后置为头结点继续翻译
            	
        	}
        	scanf("%c", &c);
        	
    }
     
    if ((HT[i].lchild != 0 || HT[i].rchild != 0) && i != max_node){
        printf("\n编码有误,以下是错误的译文!!!");
    }
    Decode[k] = '\0';		
}

//启用 
//哈夫曼译码
void HuffmanDecoding(HuffmanTree &HT,char a[],char zf[],char b[],int n) {
    //a[]用来传入二进制编码,b[]用来记录译出的字符
    //zf[]是与哈夫曼树的叶子对应的字符,n是字符个数相当于zf[]数组的长度
    int q=2*n-1;                 //q初始化为根结点的下标
    int k=0;                      //记录存储译出字符数组的下标
    for(int i=0; a[i]!='\0'; i++) {  //for循环结束条件是读入的字符是结束符
                                     //判断读入的二进制字符是0还是1
        if(a[i]=='0') {
                                  //读入0,把根结点(HT[q])的左孩子的下标值赋给q,
                                  //下次循环的时候把HT[q]的左孩子作为新的根结点
            q=HT[q].lchild;
        } else if(a[i]=='1') {
                                    //读入1,把根结点(HT[q])的右孩子的下标值赋给q,
                                    //下次循环的时候把HT[q]的右孩子作为新的根结点
            q=HT[q].rchild;
        }
                                     //判断HT[q]是否为叶子结点
        if(HT[q].lchild==0 && HT[q].rchild ==0) {
                                     //如果读到一个结点的左孩子和右孩子都为0,是叶子结点,
                                     //说明已经译出一个字符,该字符的下标就是找到的叶子结点的下标
            b[k++]=zf[q];            //把下标为q的字符赋给字符数组b[]
            q=2*n-1;                 //初始化q为根结点的下标
        }                          //继续译下一个字符的时候从哈夫曼树的根结点开始
    }
    //译码完成之后,用来记录译出字符的数组由于没有结束符输出的时候会报错,
    //所以紧接着把一个结束符加到数组最后
    b[k] = '\0';
}

void menu() {
    int n,k = 0,max_node = 0;           //记录要编码的字符个数
    char a[MAX_MA];   //储存输入的二进制字符
    char b[MAX_ZF];   //存储译出的字符
    char zf[MAX_ZF];  //储存要编码的字符
    char c;
    HuffmanTree HT;   //初始化树为空树
    HuffmanCode HC;   //初始化编码表为空表
    numCount numc[27];
	//接收键盘输入的字母存储起来
	char code[999];
	//这个是解码后存储的数组就存在这里
	char *Decode;
    while(1) {
        system("date/t");
        system("time/t");
        cout<<" =====================================================\n";
        cout<<"            哈夫曼编码与译码          \n";
        cout<<"======================================================\n";
        cout<<"======================================================\n";
        cout<<"        【1】--- 创建哈夫曼树         \n";
        cout<<"        【2】--- 进行哈夫曼编码       \n";
        cout<<"        【3】--- 进行哈夫曼译码       \n";
        cout<<"        【4】--- 退出程序             \n";
        cout<<" =====================================================\n";
        cout<<"请输入数字来选择对应的功能:";
        int num;
        if(!(cin>>num)) {
            system("CLS");
            cout<<"输入格式错误!请重新输入:"<<endl;
               cin.clear();
               cin.sync();
               cin.ignore();
        } else {
            switch(num) {
                case 1:
					int i;
					char ch;     //ch用来每次接收一个字符
					n = 0;
                    zfInit(numc);   //初始化字母
					printf("请输入一段英文或中文:\n");
					scanf("%c",&ch);
					while(ch!='#')
						{
                            if(ch>='a' && ch<='z')
                            {
                                numc[ch-97+1].count++;
                                n++;
                            }
                            if(ch >='A' && ch<='Z') 
                            {
                                numc[ch-65+1].count++;
                                n++;
                            }
                            code[n] = ch;
							scanf("%c",&ch);
						}
						printf("\n");
						//动态分配数组 
						Decode = (char *)malloc(sizeof(char) * n);							

						for(i=1;i<=MAX_LENGTH;i++)
						{
							if(numc[i].count>0)   //只输出输入过的字母统计
								printf("%c:%d\n",numc[i].data,numc[i].count);	
						}
					 
                    k =  CreatHuffmanTree(HT,MAX_LENGTH,numc);
                    //最大生成节点个数 
					max_node = 2*k-1;
					
                    cout<<"创建哈夫曼成功!下面是该哈夫曼树的参数输出:"<<endl;
                    
                    cout<<"下标"<<"\t"<<"字符"<<"\t"<<"权值"<<"\t"<<"双亲"<<"\t"<<"左孩子"<<"\t"<<"右孩子"<<endl;
					
                    for(i=1; i<=max_node; i++) {

						
							printf("%d\t%c\t%d\t%d\t%d\t%d",i,HT[i].info,HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild);
							printf("\n");
					
                    }
                   
                    getch();
                    system("CLS");
                    break;
                case 2:
                	
                    system("CLS");
                     
                    HuffmanCoding(HT, HC, max_node);
                    for(i=1;i<=k;++i){
					if(HT[i].weight>0){
					
						printf("节点信息:%c\t字符编码:%s\n",HT[i].info,HC[i]);
					}
				}
				
                 printf("译码为:\n");
                 for(i=1; i<=n;i++){  
                 //把键盘接收的数组赋值到c 
				 		c = code[i];
				 	
        			for(int j=1;j<=k;j++){
        				//判断 c的字 是否在 HT里面存在  如果存在就输出a的编码 
        				if(c == HT[j].info){     						
        					printf("%s",HC[j]);
        					break;
						}		
					}
				}
                    getch();
                    system("CLS");
                    break;

                case 3:
                    system("CLS");
                    cout<<"请输入想要翻译的一串二进制编码:";
                    //解码 
                    HafumanTreeDecode(HT,Decode,max_node);
                	printf("%s", Decode);	
    				printf("\n编译译码完成...");

                    getch();
                    break;
                case 4:
                    exit(0);
                    break;
                default:
                    system("CLS");
                    cout<<"输入错误!没有此功能!请重新输入!"<<endl;
                    cout<<endl;
            }
        }
    }
}

int main() {
    menu();
    return 0;
}


总结

提示:这里对文章进行总结:

实训中碰到的难题:在构造哈夫曼树前,需要统计输入英文段中的每个字母和每个字母出现的次数作为叶子结点和权值,将统计后的结果输出,传入构造哈夫曼树函数中构造哈夫曼树,输出构造成功后的哈夫曼树的结点、双亲、孩子、权值等参数。实训中因为字母初始化后传值、调用问题,一直实现不了输出正确的字母统计和哈夫曼树的参数,导致后面编码和译码功能不能正常实现

你可能感兴趣的:(笔记,数据结构,c++,霍夫曼树)