哈夫曼编码

来源:互联网 发布:网络销售食品药品 编辑:程序博客网 时间:2024/06/07 00:12

一、问题引入
哈夫曼编码是可变字长编码(VLC)的一种。该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码。
简言之就是,我们的一段话中,每个字母都会有它的出现频率,如果出现频率很高的字母,我们可以用很短的比如0表示,而出现频率很低的字母,我们用10000或者100001之类的编码,可以使得整体的编码长度,大大下降。
并且要注意的是,我们常常会遇到扩展码的情况,就是10000100001这样的,如果这个时候不能保证一个码字不是任何一个码字的前缀码,他就会有歧义,是翻译成10000对,还是翻译成100001对呢?这些问题可以用哈夫曼来解决。
附:很可能得出不同的答案,但是平均编码长度是一样的。
二、算法思想
1.输入并计算词频
2.构建哈夫曼树:算法基本思想是循环的选择具有最低频率的两个节点,生成一颗子数,直至形成数(例题如下:)
这里写图片描述
3.根据哈夫曼树编码
按输入次序i=0到n寻找对应的树底。P=h[i].parent,再h[p].parent向上寻找,直到顶端
4.根据哈夫曼树译码
按输入的0110101010等次序,从树顶开始往下找,直到达到叶节点,开始又一轮的从树顶开始,这里要注意如果输入到了末尾,却依然没有到达叶节点(树底),直接判错
三、具体代码

#include<iostream>#include<string>#define maxvalue 100000using namespace std;typedef struct{    bool bit[100];//存储编码    int start; //这个节点编码需要多少个bit    int weight;//这个节点字符的权重    int parent;//这个节点的父节点    int lchild;    int rchild;    char ch;//这个节点字符}hnode;void decoded(hnode *tree,string b,int n)//依次读入电文,根据哈夫曼树译码{    int i,j=0;int flag;    char endflag='2'; //电文结束标志取2    i=2*n; //从根结点开始往下搜索,2*N是因为(根节点的下标+1)=(2*输入不重复字符数-1),即i+1=2*(n+1)-1     printf("译码后的字符为");    while(b[j]!='2')    {        flag=0        if(b[j]=='0')            i=tree[i].lchild; //走向左孩子        else            i=tree[i].rchild; //走向右孩子        if(tree[i].lchild==-1) //tree[i]是叶结点        {            flag=1;//本轮到达叶子节点            printf("%c",tree[i].ch);            i=2*n; //回到根结点        }        j++;    }    printf("\n");    if(flag==0&&b[j]=='2') //电文读完,但尚未到叶子结点    printf("\nERROR\n"); //输入电文有错}//decodeint huff(hnode *h,int nch)//构建哈夫曼树,注意传进来的n+1,是不同的字符数 {    int i,j,m1,m2,x1,x2;    for(int i=0;i<2*nch-1;i++)    {        h[i].parent=-1;h[i].lchild=-1;h[i].rchild=-1;    }    for(int i=0;i<nch-1;i++)//接下来每一轮的值之前的两者最小值相加,而每一次都会选出两个字符(合并过的父节点或者叶节点都无妨)去合并处理,会发现,最后总共要n-1轮这样的操作    {        m1=m2=maxvalue;        x1=x2=0;        for(int j=0;j<nch+i;j++)        {            if(h[j].weight<m1&&h[j].parent==-1)//权值小且没有父节点             {                m2=m1;//m1虽然当不了最小,但它有可能是次小,值要先保留给m2                 x2=x1;                m1=h[j].weight;                x1=j;            }            else if(h[j].weight<m2&&h[j].parent==-1)            {                m2=h[j].weight;                x2=j;            }        }        h[x1].parent=nch+i;        h[x2].parent=nch+i;        h[nch+i].weight=h[x1].weight+h[x2].weight;        h[nch+i].lchild=x1;        h[nch+i].rchild=x2;    }}void code(hnode* h ,int n){    int x=1000;    hnode temp;int t,p,j;    for(int i=0;i<=n;i++)    {        temp.start=x;//其实这个初值可以随意,主要是体现栈的思想。因为是从树底下往上一直找到树顶,所以是倒着的存储。        t=i;         p=h[t].parent;        while(p!=-1)        {            if(h[p].lchild==t)//如果是父节点的左孩子节点,置0                 temp.bit[temp.start]=0;            else temp.bit[temp.start]=1;//如果是父节点的右孩子节点,置1             temp.start--;            t=p;             p=h[t].parent;        }//所以temp.bit[j]的范围就是从(最后的temp.start+1到它原来的初值n-1)        for(j=temp.start+1;j<=x;j++)            h[i].bit[j-temp.start-1]=temp.bit[j];         h[i].start=x-temp.start;//temp.bit[i]有值的范围就是它有几位数    }    for(int i=0;i<=n;i++)    {        cout<<h[i].ch<<"  ";        for(int j=0;j<h[i].start;j++) cout<<h[i].bit[j];        cout<<endl;    }}int  input(string str,hnode* h){    cin>>str;    int count=0;    h[0].ch=str[0];    h[0].weight=1;    for(int i=1;i<str.length();i++)    {        int flag=1;        for(int j=0;j<=count;j++)        {            if(str[i]==h[j].ch)            {                h[j].weight++;                flag=0;                break;            }        }        if(flag==1)//如果这是一个全新的字符         {            count++;            h[count].ch=str[i];            h[count].weight=1;         }    }    return count;//count是最后下标,count+1是有几个不同字符}int main(){    hnode h[1000], temp;    int i,j,t,p,n;    string str,decode;    printf("输入字符串并以回车结尾");    n=input(str,h);    huff(h,n+1);//构建哈夫曼树    code(h,n);//根据构建的哈夫曼树 编码    printf("输入发送的编码(以'2'为结束标志)");    cin>>decode;    decoded(h,decode,n);//根据构建的哈夫曼树译码}

四、实验结果
这里写图片描述
这里写图片描述

0 0