霍夫曼树(类实现)——超详细解释,超通俗讲解

本文介绍了一种霍夫曼树编码的实现方法,通过构建霍夫曼树来实现最短编码,包括霍夫曼树的构建过程及编码查找算法,并提供了一个完整的C++实现示例。

霍夫曼树常用于最短编码,各类数据结构教材中也都有涉及,核心思想就是每次挑选值最小的树节点,将之合成为一个节点,值为两个之和,取代原来两个,直到只有一个树节点——也就是根。
数据结构很简单,下面附上类实现的代码(编译环境:vs2010)

头文件:

#pragma once
class btree
{
public:
    struct treenode{
        //树节点的数据域
    char data;
    int number;
    //树节点的左右子树指针
    treenode*left,*right,*parent;
    }*root;//根结点
    btree(void);//构造函数
    btree(char strings[],int cnt[],int n);
    btree(char pre[],char inn[],int flag);//构造函数重载

    //析构函数
    ~btree(void);
    //复制构造函数
    btree(btree &s);

    //逐层输入构造二叉树
    treenode* create(char start[],int n,int pos);

    void provisit();//前序遍历
    void provisit(treenode*cur);//重载前序遍历
    void invisit();//中序遍历
    void invisit(treenode*cur);//重载中序遍历
    void postvisit();//后序遍历
    void postvisit(treenode*cur);//重载后序遍历
    void levelorder();//层序遍历
    void levelorder(treenode*cur);//重载层序遍历

    void destory(treenode*cur);//删除结点指向的子树

    void creattree(int flag);//二叉树构建函数

    //用先序遍历和中序遍历构造子树
    treenode* creatree(char pre[],int prelength,char in[]);

    //复制该节点指向的子树
    treenode* copytree(treenode *c);

    //进行排序操作,使得节点有序
    void popsort(treenode* goal[],int length);

    //找到目标字符的编码
    void findcode(char goal);

    //在第pos层,cur指向的子树中找到目标字符函数的编码,
    bool foundcode(char goal,treenode*cur,char code[],int pos);

    //找到目标字符的长度
    int findlength(char goal);

    //在指针cur指向的子树中找到目标字符
    int findlength(char goal,treenode*cur);

    //找到总长度
    int totallength(char str[],int cnt[],int length);
};

类的源文件:

#include "StdAfx.h"
#include "btree.h"
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
#define maxsizenode 100
//********************************************************************************
//函数名:btree
//函数参数:void
//函数返回类型:
//函数功能:构造函数
//备注:默认构造函数,只把根节点置空
//********************************************************************************
btree::btree(void)
{
    root=NULL;//根节点置空
}
//********************************************************************************
//函数名:btree
//函数参数:char pre[],char inn[]分别是先序遍历和中序遍历的字符串数组
//函数返回类型:
//函数功能:当参数flag为1时,用先序遍历和中序遍历构造二叉树;为2时,采用后序遍历和中序遍历建立
//备注:这是类型1的构造函数实现
//********************************************************************************
btree::btree(char pre[],char inn[],int flag)
{
    if(flag==1)
    root=creatree(pre,int(strlen(pre)),inn);//调用函数进行初始化
    if(flag==2){}//没有要求具体实现所以省略
}


//********************************************************************************
//函数名:btree
//函数参数:char strings[],int cnt[],int n ;strings表示待输入的字符串,cnt为字符串中每个对应位置字母的出现次数,n表示字母个数
//函数返回类型:无
//函数功能:构造函数
//备注:用于构建一个霍夫曼树
//********************************************************************************
btree::btree(char strings[],int cnt[],int n)
{

    //计数器
    int i,j=0;

    //构建一个treenode指针数组存储每个霍夫曼树节点
    treenode *memory[maxsizenode],*cur;

    //根据字符串与对应出现次数建立霍夫曼树节点
    for(i=0;i<n;i++)
    {

        //新建立节点
        memory[i]=new treenode;

        //对该节点进行赋值和预处理
        memory[i]->left=memory[i]->right=NULL;
        memory[i]->data=strings[i];
        memory[i]->number=cnt[i];
    }


    do
    {

        //对于所有节点,按照出现次数从小到大排序
        popsort(memory,n-j);

        //建立新节点,新节点的值即为最小的两个子树的值的加和,最小的两个节点分别为memory[0]和memory[1];
        cur=new treenode;
        cur->number=memory[0]->number+memory[1]->number;
        cur->data='\0';

        //新节点左右子树分别连到原memory[0]和memory[1],使memory[0]和memory[1]成为一个新子树
        cur->left=memory[0];
        cur->right=memory[1];
        memory[0]->parent=cur;
        memory[1]->parent=cur;

        //把原memory[1]的位置换成cur,则新的有效节点为从memory[1]到memory[n-j],通过循环把节点依次左移一位,j记录该操作次数
        memory[1]=cur;
        int t;
        for(t=0;t<n-1-j;t++)
        {
            memory[t]=memory[t+1];
        }
        j++;
    }while(j!=n-1);//当只剩有一个有效子树节点时,退出

    //所有的子树已经连为一个树,树的根节点就是memory【0】
    root=memory[0];
}


//********************************************************************************
//函数名:findnode
//函数参数:char goal表示需要找的字母元素
//函数返回类型:void
//函数功能:在霍夫曼树中找到这个字母并输出该字母霍夫曼编码
//备注:当元素不存在时输出错误提醒
//********************************************************************************
void btree::findcode(char goal)
{
    //用于存储编码字符串
    char code[100]={'\0'};
    int pos=0;

    //输出错误提醒
    if(!foundcode(goal,root,code,pos))
        cout<<"not found"<<endl;
    //找到后输出霍夫曼编码
    else 
    {
        cout<<code;
    }
}


//********************************************************************************
//函数名:foundcode
//函数参数:char goal,treenode*cur,char code[],int pos;goal记录待查找字符,
//            cur记录当前树节点,code用于记录霍夫曼编码,pos记录code新值的存储位置
//函数返回类型:bool,true表示在当前cur指向的节点及其子树中找到目标元素,false表示没有找到
//函数功能:在当前cur指向的节点及其子树中找到目标元素,并且记录路径,即霍夫曼编码
//备注:无
//********************************************************************************
bool btree::foundcode(char goal,treenode*cur,char code[],int pos)
{
    //if(cur->data==goal) return true;
    if(cur->left!=NULL&&foundcode(goal,cur->left,code,pos+1))
    {
        code[pos]='0';
        return true;
    }
    if(cur->right!=NULL&&foundcode(goal,cur->right,code,pos+1))
    {
        code[pos]='1';
        return true;
    }
    if(cur->data==goal) 
    {
        return true;
    }
    return false;
}
//********************************************************************************
//函数名:foundlength
//函数参数:char goal;goal记录待查找字符,
//函数返回类型:int 为目标元素编码长度
//函数功能:在整个霍夫曼树中查找目标元素
//备注:默认找得到
//********************************************************************************
int btree::findlength(char goal)
{


    //由于子函数中查找时按节点计数,故-1
    return  findlength(goal,root)-1;
}


//********************************************************************************
//函数名:foundlength
//函数参数:char goal,treenode*cur;goal记录待查找字符,cur记录当前树节点
//函数返回类型:int 返回值表示当前节点到目标元素的路径长度
//函数功能:在当前cur指向的节点及其子树中找到目标元素,并且记录长度
//备注:当该子树下不存在时,返回值为0
//********************************************************************************
int btree::findlength(char goal,treenode*cur)
{

    //当左子树不为空,且目标元素位于左子树中时
    if(cur->left!=NULL&&findlength(goal,cur->left))
    {

        //返回当前节点到目标元素的长度+1
             return 1+findlength(goal,cur->left);
    }

    //右子树处理同左子树
    if(cur->right!=NULL&&findlength(goal,cur->right))
    {

        return 1+findlength(goal,cur->right);
    }

    //如果当前节点就是目标元素,返回1
    if(cur->data==goal)
        return 1;
    //否则返回0表示找不到
    else return 0;
}


//********************************************************************************
//函数名:totallength
//函数参数:char str[],int cnt[],int length str表示构建正课霍夫曼树时用到的字符串,cnt对应字母表示出现次数,length表示个数
//函数返回类型:int 返回整棵霍夫曼树的编码长度
//函数功能:求得整棵霍夫曼树的编码长度
//备注:默认子树存在且有效,str,cnt和原始输入一样,不能改变
//********************************************************************************

int btree::totallength(char str[],int cnt[],int length)
{
    //记录总长度
    int sum=0;
    for(int i=0;i<length;i++)
    {

        //把每个元素出现的次数乘以编码长度,累加
        sum+=cnt[i]*findlength(str[i]);
    }
    return sum;
}
//********************************************************************************
//函数名:~btree
//函数参数:无
//函数返回类型:
//函数功能:析构函数
//备注:调用删除子树的函数从根节点开始删除
//********************************************************************************
btree::~btree(void)
{
    destory(root);//调用节点删除函数进行析构
}
//********************************************************************************
//函数名:btree
//函数参数:btree&s 另一个btree类的对象
//函数返回类型:
//函数功能:复制构造函数
//备注:调用复制子树的函数从根节点开始复制
//********************************************************************************
btree::btree(btree &s)
{
    root=copytree(s.root);//调用子树复制函数进行复制构造过程
}
//********************************************************************************
//函数名:cpoytree
//函数参数:treenode*c 指向树节点的指针
//函数返回类型:treenode* 
//函数功能:复制c指向的子树
//备注:c子树不为空
//********************************************************************************
btree::treenode* btree::copytree(treenode *cc)
{
    //如果该子树为空,返回空指针
    if(cc==NULL) 
        return NULL;
    //建立当前节点
    treenode *cur;
    cur=new treenode;
    cur->data=cc->data;
    //当左子树为空,直接置空
    if(cc->left==NULL) 
        cur->left=NULL;
    //否则递归建立左子树
    else cur->left=copytree(cc->left);
    //右子树过程与左子树相同
    if(cc->right==NULL) 
        cur->right=NULL;
    else cur->right=copytree(cc->right);
    //返回新建立节点的指针
    return cur;
}
//********************************************************************************
//函数名:destory
//函数参数:treenode*cur
//函数返回类型:无
//函数功能:删除该节点指向的子树
//备注:无
//********************************************************************************
void btree::destory(treenode*cur)
{
    //如果左子树不为空,则递归删除左子树,最后把左子树置空
    if(cur->left!=NULL)
    {
        destory(cur->left);
        cur->left=NULL;
    }
    //右子树处理类似左子树
    if(cur->right!=NULL)
    {
        destory(cur->right);
        cur->right=NULL;
    }
    //删除当前节点
    delete cur;
}

//********************************************************************************
//函数名:create
//函数参数:char start【】,int n,int pos
//函数返回类型:treenode*
//函数功能:把数组start的字符按照层次序输入到二叉树中
//备注:无
//********************************************************************************

btree::treenode* btree::create(char start[],int n,int pos)
{
    treenode*rot;
    if(n>pos)
    {
        rot=new treenode;
        rot->data=start[pos-1];//建立当前节点
        //按照完全二叉树建立左右子树
        rot->right=create(start,n,2*pos+1);
        rot->left=create(start,n,2*pos);
        return rot;
    }
    else return NULL;
}

//********************************************************************************
//函数名:provisit
//函数参数:无
//函数返回类型:无
//函数功能:实现整棵树的先序遍历
//备注:调用其重载函数实现
//********************************************************************************
void btree::provisit()
{
    provisit(root);//调用重载函数
    cout<<endl;
}
//********************************************************************************
//函数名:provisit
//函数参数:treenode*cur
//函数返回类型:无
//函数功能:实现某节点开始的先序遍历
//备注:无
//********************************************************************************
void btree::provisit(treenode*cur)
{
    if(cur!=NULL)
        {cout<<cur->data<<' ';

        provisit(cur->left);


        provisit(cur->right);}

}
//********************************************************************************
//函数名:invisit
//函数参数:无
//函数返回类型:无
//函数功能:实现整棵树的中序遍历
//备注:调用其重载函数实现
//********************************************************************************
void btree::invisit()
{
    invisit(root);//调用重载函数
    cout<<endl;
}
//********************************************************************************
//函数名:invisit
//函数参数:treenode*cur
//函数返回类型:无
//函数功能:实现某节点开始的中序遍历
//备注:无
//********************************************************************************
void btree::invisit(treenode*cur)
{
    if(cur!=NULL){
    invisit(cur->left);

    cout<<cur->data<<' ';

    invisit(cur->right);}

}
//********************************************************************************
//函数名:postvisit
//函数参数:无
//函数返回类型:无
//函数功能:实现整棵树的后序遍历
//备注:调用其重载函数实现
//********************************************************************************
void btree::postvisit()
{
    postvisit(root);//调用重载函数
    cout<<endl;
}
//********************************************************************************
//函数名:postvisit
//函数参数:treenode*cur
//函数返回类型:无
//函数功能:实现某节点开始的后序遍历
//备注:无
//********************************************************************************
void btree::postvisit(treenode*cur)
{
    if(cur!=NULL)
    {
        postvisit(cur->left);
        postvisit(cur->right);

        cout<<cur->data<<' ';}
}
//********************************************************************************
//函数名:levelorder
//函数参数:无
//函数返回类型:无
//函数功能:实现某节点开始的层次遍历
//备注:无
//********************************************************************************
void btree::levelorder()
{
    levelorder(root);//调用重载函数
    cout<<endl;
}
//********************************************************************************
//函数名:levelorder
//函数参数:treenode*cur
//函数返回类型:无
//函数功能:实现某节点开始的层次遍历
//备注:无
//********************************************************************************
void btree::levelorder(treenode*cur)
{

    treenode*pp=cur;
    //利用循环队列实现
    int r=0,l=0;
    treenode*queue[100];
    queue[0]=cur;//第一个进入
    r++;//右端右移一
    while(l<r)//当队列不为空
    {

        pp=queue[l%100];//保存队列首个元素
        cout<<queue[l%100]->data<<' ';//输出
        l++;//左端出队
        if(pp->left!=NULL)
        {

            queue[r%100]=pp->left;//左子树进队
            r++;
        }
        if(pp->right!=NULL)
        {

            queue[r%100]=pp->right;//右子树进队
            r++;
        }
    }
}
//********************************************************************************
//函数名:creattree
//函数参数:int flag
//函数返回类型:无
//函数功能:作为普通的成员函数,按照第一种类型构造二叉树
//备注:无
//********************************************************************************
void btree::creattree(int flag)
{
    //作为普通成员函数,和构造函数区别,过程一样
    if(flag==1)
    {
    char pre[1000]={'\0'},inn[1000]={'\0'};
    cout<<"请输入先序遍历"<<endl;
    cin>>pre;
    cout<<"请输入中序遍历"<<endl;
    cin>>inn;
    root=creatree(pre,int(strlen(pre)),inn);
    }
}
//********************************************************************************
//函数名:creatree
//函数参数:char *pre,int prelength,char *in,int inlength
//函数返回类型:treenode* 
//函数功能:用先序遍历和中序遍历构造二叉树
//备注:无
//********************************************************************************
btree::treenode* btree::creatree(char *pre,int prelength,char *inn)
{
    if(prelength<=0) return NULL;//如果当前数组长度为0,即已经构造完成,则返回NULL
    if(pre[0]=='\0')return NULL;//如果当前数组第一个元素也是空,同样表明构造完成
    //建立当前节点,赋值data,左右子树指针
    treenode *newnode;
    newnode=new treenode;
    newnode->data=*pre;
    newnode->left=NULL;
    newnode->right=NULL;
    //在中序遍历中找到前序遍历的第一个字符
    int i;
    for(i=0;i<=prelength;i++)
    {
        if(inn[i]==*pre)
        {
            break;//找到之后退出
        }
    }
    //对于中序遍历,把当前对应的先序遍历的第一个元素作为划分标准分为左子树和右子树,并把前序遍历中和中序遍历左子树对应的部分一同作为左子树构建参数
    //剩余部分作为右子树参数
    //递归建立左子树,并把前序遍历和中序遍历做相应的处理
    newnode->left=creatree(pre+1,i,inn);
    //递归建立右子树,并把前序遍历和中序遍历做相应的处理
    //前序遍历从原遍历的i+1个开始,中序遍历从找到位置之后一个开始

    newnode->right=creatree(pre+1+i,prelength-1-i,inn+i+1);
    //返回当前节点指针
    return newnode;
}

void btree::popsort(treenode* goal[],int length)
{
    int i,j;
    treenode* cur;
    for(i=0;i<length;i++)
    {
        for(j=i+1;j<length;j++)
        {
            if(goal[j]->number<goal[i]->number)
            {
                cur=goal[i];
                goal[i]=goal[j];
                goal[j]=cur;
            }
        }
    }
}

测试程序:

// .cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "btree.h"
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;

char strings[100],strings2[100];//strings表示初始化霍夫曼树的字符存储,strings2表示待编码的字符串

//记录字母出现次数
int cnt[100];
int _tmain(int argc, _TCHAR* argv[])
{
    int t=26;//字母个数,默认26个字母

    ifstream inf("char.txt");

    //文件打开成功
    if(!inf.fail())
    {

        //输入的字母和出现次数分别保存在两个数组中
        //strings存储字母,cnt存储出现次数
        for(int i=0;i<t;i++)
        {
            inf>>strings[i]>>cnt[i];
        }

        //霍夫曼树初始化
        btree btreetest1(strings,cnt,t);

        //输出每个字母的编码
        for(int i=0;i<t;i++)
        {
            cout<<strings[i]<<": ";

            //调用函数找到编码
            btreetest1.findcode(strings[i]);
            cout<<endl;
        }

        //总体编码
        cout<<strings<<"霍夫曼编码"<<' ';
        for(int i=0;i<t;i++)
        {
            btreetest1.findcode(strings[i]);
        }
        cout<<endl;
        cout<<"霍夫曼编码总长度"<<btreetest1.totallength(strings,cnt,t)<<endl;



        cout<<"请输入待编码的字符串"<<endl;

        //存储带编码的字符串
        cin>>strings2;
        int lengths= strlen(strings2);

        //找到每个字母编码
        for(int i=0;i<lengths;i++)
        {
            cout<<strings2[i]<<": ";
            btreetest1.findcode(strings2[i]);
            cout<<endl;
        }

        cout<<strings2<<"霍夫曼编码"<<' ';


        for(int i=0;i<lengths;i++)
        {
            btreetest1.findcode(strings2[i]);
        }
        cout<<endl;


        int sum=0;

        //长度累加
        for(int i=0;i<lengths;i++)
        {
            sum+=btreetest1.findlength(strings2[i]);
        }
        cout<<"霍夫曼编码总长度: "<<sum<<endl;
    }

    //文件打开失败
    else 
    {
        cout<<"文件打开失败,请手动输入"<<endl;
        cout<<"请输入数据个数"<<endl;
        int m;
        cin>>m;
        cout<<"请输入字符及其出现次数"<<endl;
        for(int i=0;i<m;i++)
        {
            cin>>strings[i]>>cnt[i];
        }
        btree btreetest2(strings,cnt,m);

    }
    inf.close();
    system("pause");
    return 0;
}

对于程序的测试:
1.首先将需要输入的文件放入项目文件夹下,示例如图:
这里写图片描述
2.接下来输入需要编码的字符串
3.然后就是开心地看运行结果啦~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值