树与二叉树(三)

目录

哈夫曼树及其应用

哈夫曼树的基本概念

哈夫曼树的构造算法

构造哈夫曼树

哈夫曼编码


哈夫曼树及其应用

哈夫曼树的基本概念

哈夫曼树又称为最优树(最优树---带权路径长度(WPL)最短的树。是一类带权路径长度最短的树在实际中有广泛的用途。哈夫曼树的定义,涉及路径、路径长度、权等概念,下面先给出这些概念的定义,再介绍哈夫曼树。

(1) 路径:从树中一个节点到另一个节点之间的分支构成这两个节点之间的路径。

(2) 路径长度:路径上的分支数目称作路径长度。

(3) 树的路径长度:从树根到每一叶子节点的路径长度之和。

(4) :赋予某个实体的一个量,是对实体的某个或某些属性的数值化描述。在数据结构中,实体有节点(元素)和边(关系)两大类,所以对应有节点权和边权。节点权或边权具体代表什么意义,由具体情况决定。如果在一棵树中的节点上带有权值,则对应的就有带权树等概念。

(5) 节点的带权路径长度:从该节点到树根之间的路径长度与节点上权值的乘积。

(6)树的带权路径长度:树中所有叶子节点的带权路径长度之和。

(7)哈夫曼树:假设有m个权值{w1,w2,…,wm},可以构造一棵含n个叶子节点的二叉树,每个叶子节点的权值为wi,则其中带权路径长度WPL最小的二叉树称作最优二叉树或哈夫曼树。

注:哈夫曼树不唯一。

哈夫曼树的构造算法

(1)根据给定的n个权值{w1,w2,……wn},构造n棵只有根结点的二叉树。  构造森林全是根

(2)在森林中选取两棵根结点权值最小的树作左右子树,构造一棵新的二叉树,置新二叉树根结点权值为其左右子树根结点权值之和。  选用两小造新树

(3)在森林中删除这两棵树,同时将新得到的二叉树加入森林中。  删除两小添新人

(4)重复上述两步,直到只含一棵树为止,这棵树即哈夫曼树。

哈夫曼树算法口诀:

构造森林全是根, 选用两小造新树 ,删除两小添新人 ,重复2,3剩单根

 结论:

1.在哈夫曼算法中,初始时有n棵二叉树,要经过n-1次合并最终形成哈夫曼树。

 2. 经过n-1次合并产生n-1个新结点,且这n-1个新结点都是具有两个孩子的分支结点。 可见:哈夫曼树中共有n+n-1=2n-1个结点,且其所有的分支结点的度均不为1。

哈夫曼树算法的实现

采用顺序存储结构---一维结构数组。

其节点类型定义

typedef struct
{
    int weight;            //节点的权值
    int parent,lchild,rchild;  //节点的双亲、左孩子、右孩子的下标
}HTNode,*HuffmanTree;          //动态分配数组存储哈夫曼树

哈夫曼树的各节点存储在由 HuffmanTree定义的动态分配的数组 中,为了实现方便,数组的
0号单元不使用,从1号单元开始使用,所以数组的大小为2n。将叶子节点集中存储在前面部分的n个位置,而后面的n-1个位置存储其余非叶子节点。

构造哈夫曼树

算法思路

1.初始化:首先动态申请2n个单元;然后循环2n-1次,从1号单元开始,依次将1至2n-1所有单元中的双亲、左孩子、右孩子的下标都初始化为0;最后循环次,输人前n个单元中叶子节点的权值。

2.创建树:循环n-1次,通过n-1次的选择、删除与合并来创建哈夫曼树。选择是从当前森林中选择双亲为0且权值最小的两个树根节点s1和s2;删除是指将节点s1和Is2的双亲改为非0;合并就是将s1和s2的权值和作为一个新节点的权值依次存人数组的n+ 1号及之后的单元中,同时记录这个新节点左孩子的下标为sl,右孩子的下标为s2。

void CreateHuffmanTree (HuffmanTree &HT, int n)
{ //构造哈夫曼树HT
    if(n<=1) return;
    m=2*n-1;
    HT=newHTNode [m+1];  //0号单元未用,所以需要动态分配m+1个单元,HT [m]表示根节点
    for(i=1;i<=m;++i)   //将1~m号单元中的双亲、左孩子,右孩子的下标都初始化为0
    {
        HT[i].parent=0;HT[i] .1child=0;HT[i] . rchild=0;
    }
    for(i=1;i<=n;++i)  //输人前n个单元中叶子节点的权值
    cin>>HT[i] .weight;

/*初始化工作结束,下面开始创建哈夫曼树*/

    for(i=n+1;i<=m;++i)
    {//通过n-1次的选择、删除、合并来创建哈夫曼树
    Select (HT,i-1,s1,s2);
     //在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的节点,并返回它们在旺中的序号s1和s2
    HT[sl] .parent=i;HT[s2] .parent=i;
    //得到新节点i,从森林中删除s1, s2,将s1和s2的双亲域由0改为立
    HT[i]. lchild=s1;HT[i].rchild=s2;
    //s1,s2分别作为i的左右孩子
    HT[i]. weight=HT[s1]. weight+HT[s2] .weight;//i的权值为左右孩子权值之和
    }
}

哈夫曼编码

哈夫曼编码的重要思想

出现次数较多的字符采用尽可能短的编码。

哈夫曼编码设计的关键:要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码的前缀-前缀编码。

1.前缀编码:如果在一个编码方案中,任一个编码都不是其他任何编码的前缀(最左字串),则称编码是前缀编码。

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

什么样的前缀编码能使得报文总长度最短?          

哈夫曼编码方法:

1、统计字符集中每个字符在电文中出现的平均概率(概率越大,要求编码越短)。

2、利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈弗曼树。则概率越大的结点,路径越短。

3、在哈夫曼树的每个分支上标上0或1。   

结点的左分支标0,右分支标1;    

把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码。

哈夫曼编码算法的实现

算法思路

1、分配存储n个字符编码的编码表空间HC,长度为n+1;分配临时存储每个字符编码的动态数组空间cd,cd[n-1]置为‘\0’。

2、逐个求解n个字符的编码循环n次,执行以下操作:
~设置变量start用于记录编码在cd中存放的位置,start初始时指向最后,即编码结束符位
置n-1;

~设置变量c用于记录从叶子节点向上回溯至根节点所经过的节点下标,c初始时为当前待
编码字符的下标i,f用于记录i的双亲节点的下标;

~从叶子节点向上回溯至根节点,求得字符i的编码,当f没有到达根节点时,循环执行以
下操作:
回溯一次start向前指一个位置,即--start;
若节点c是f的左孩子,则生成代码0,否则生成代码1,生成的代码0或1保存在cd[start]中;
继续向上回溯,改变c和f的值。
~根据数组cd的字符串长度为第i个字符编码分配空间HC[i],然后将数组cd中的编码复制到HC[i]中。

3、 释放临时空间cd。 

void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n){
//从叶子到根逆向求每个字符的赫夫曼编码,存储在编码表HC中
HC=new char *[n+1];         		//分配n个字符编码的头指针矢量
cd=new char [n];			//分配临时存放编码的动态数组空间
cd[n-1]=’\0’; 	//编码结束符
for(i=1; i<=n; ++i){	//逐个字符求赫夫曼编码
    start=n-1; c=i; f=HT[i].parent;                 			
    while(f!=0){	//从叶子结点开始向上回溯,直到根结点
         --start;                      //回溯一次start向前指一个位置
         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;                            	//释放临时空间
} // CreatHuffanCode

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值