哈夫曼编码

来源:互联网 发布:推荐算法的选择 编辑:程序博客网 时间:2024/06/06 19:24

 

图示

算法设计

思想:

第一,从数据文件input_assign03_01.dat中读取数据,格式如下:

aabbbbccdfgfdhhg

说明:第一行代表要进行哈夫曼编码的字符串。

第二,利用贪婪算法求解。

大体分为以下几个步骤:

定义哈夫曼树节点结构体以及哈夫曼编码的结构体

typedef  struct node//定义节点

{

         int weight;//使用频度

         char letter;//字符

         int parent;//双亲节点

         int lchild;//左孩子

         int rchild;//右孩子

} huffnode;

typedef  structcode//定义节点编码

{

         int bits[32];//节点编码

         int start;//编码开始位置

} huffcode;

 

1)首先检测出输入文件中不同字符的个数n,以及各自出现的频率,从而形成哈夫曼树的n个待排叶子节点,并初始化2n-1个节点间的父子关系(n个叶子节点的哈夫曼树共2n-1个节点),存入huff_node数组中。

2)根据叶子节点的频率,构造哈夫曼树,然后进行哈夫曼编码。

构造哈夫曼树即为不断的找出当前出现频率最小的2个节点,构造出一个新节点,再加入到节点数组中去,不断迭代,从而形成一棵哈夫曼树。

在哈夫曼编码的时候,从哈夫曼树的根节点出发,左分支为0,右分支为1,从根到叶子节点的路径,即为对应叶子节点的哈夫曼编码。

算法设计:

1)统计各个字符出现的次数//输入:inputStr为输入的字符串,strLen为返回字符串的长度,hnode为叶子节点集合,n为叶子节点的个数,即不同字符的种类数void StaticFrequency(char inputStr[], int*strLen, huffnode hnode[], int *n){int i, j, k;int tag;//标签,排除统计过的字符*n=0;//统计字符出现个数, 放入hnode[*n].weightfor(i=0;inputStr[i]!='\0';i++){          tag=1;          for(j=0;j<i; j++)                    if(inputStr[j]==inputStr[i])//在第i位之前已经出现过该字符                    {                             tag=0;                             break;                    }          if(tag)//第一次出现该字符          {                    ++(*n);//字符的种类数加1                    hnode[*n].letter=inputStr[i];//将该字符存于hnode数组中                    hnode[*n].weight=1;//首次出现,将该字符出现次数置1                    for(k=i+1;inputStr[k]!='\0'; k++)//同时统计第i+1位到字符串结尾的该字符个数                             if(inputStr[i]==inputStr[k])                                      hnode[*n].weight++;//权值累加          }}*strLen =i;//字符串长度}2)构造哈夫曼树//输入:hnode为哈夫曼叶子节点,n为叶子节点的个数void HuffManTree( huffnode  hnode[],  int  n){inti,j,m1,m2,x1,x2;for(i=1;i<n; i++)//n个叶子节点,生成n-1个新的节点,哈夫曼树共2n-1个节点{          m1= m2 =MAXFREQUENCY;          x1= x2 =0;          for(j=0;j<n+i; j++)//找出出现频率最小的并且没有扩展过的两个节点          {                    if(hnode[j].parent== -1 && hnode[j].weight < m1)//hnode[j].parent == -1代表节点没有扩展过                    {                             m2= m1;                             x2= x1;                              m1= hnode[j].weight;//保存最小节点频率                             x1= j;// 保存最小频率节点的编号                    }                    elseif(hnode[j].parent == -1 && hnode[j].weight < m2)                    {                             m2= hnode[j].weight; //保存次小节点频率                             x2= j; // 保存次小频率节点的编号                    }          }//for           //当前没有扩展的两个出现频率最小的节点x1,x2组合成新的节点n+i          hnode[x1].parent= n+i;          hnode[x2].parent= n+i;          hnode[n+i].lchild= x1;          hnode[n+i].rchild= x2;          hnode[n+i].weight= hnode[x1].weight + hnode[x2].weight;//频率加和   }//for}3)对字符进行哈夫曼编码//输入:hnode为哈夫曼叶子节点,n为叶子节点的个数,hc为叶子节点对应的变长哈夫曼编码void HuffManCode( huffnode hnode[],  int n,  huffcodehc[] ){inti,j,temp,p;huffcodecd;//生成哈夫曼树HuffManTree(hnode,n);for(i=1;i<=n;i++){          cd.start=n;//从后往前存储编码,因为变长编码的长度肯定小于叶子节点的个数          temp= i;          p= hnode[temp].parent;          while(p!=-1)//只要编号p节点不是根节点          {                    if(hnode[p].lchild== temp)//左孩子为0                             cd.bits[cd.start]=0;                    else                             cd.bits[cd.start]=1;//右孩子为1                    cd.start--;                     temp= p;//保存当前节点编号                    p= hnode[p].parent;//往上找父节点          }          cd.start++;//跳出循环后cd.start多减了一次         for(j=cd.start;j<=n;j++)                    hc[i].bits[j]=cd.bits[j];//将第i个叶子节点的哈夫曼编码保存至hc数组中          hc[i].start=cd.start;//设置存储哈夫曼编码的起始位置}}


4)对输入字符串进行哈夫曼编码

在不同种类的字符哈夫曼编码后,最后就是遍历一次输入字符串,从hc[i]数组中找到每个字符对应的哈夫曼编码即可。

算法分析:

在统计字符出现频率的算法中,是一个二重循环,时间复杂度为O(len*len),len是字符串的长度。

在构造哈夫曼树的算法中,需要找出没有扩展过且当前最小的两个节点组成新节点,这里需要遍历的时间复杂度为O(n*n),n为叶子节点的个数。

最后对输入字符串编码的时间复杂度为O(len*n),因为对每一个字符都需要查找一次哈夫曼编码表。

数据结构采用了一维数组hnode[2n-1]来存储n个叶子节点形成的哈夫曼树,空间复杂度为O(n);同时利用了一个hc[n*n]的二维数组来存储每个叶子节点对应的哈夫曼编码表,空间复杂度为O(n*n)。

总结:

通过本次作业,学习巩固了三个方面的知识。

1)文件流的读写;

2)统计字符串中不同字符出现的频率。

3)通过叶子节点的频率,构造哈夫曼树,以及对应的哈夫曼编码的过程。

4)对输入的字符串进行哈夫曼编码,其中涉及到查询算法。

扩展:

1)在构造哈夫曼树的过程中,需要重复遍历已经扩展过的节点,这里就使得时间复杂度为O(n*n);

另外一种解决方案是先对解析出来的各个字符频率进行排序,然后结合的新节点插入到待扩展的节点中去,这样每次搜索只遍历待扩展的节点,前边已经扩展过的就不再访问,但是这里在排序、插入新节点方面花费了一定的时间。

2)在最后对输入字符串进行哈夫曼编码的时候,比较好的做法是将哈夫曼编码表用标准C++的map容器存储,这样可以将字符做为“键”,对应的哈夫曼编码作为“值”,利用map的find算法,可以加快一些查找的速度。

原创粉丝点击