哈夫曼树的WPL值的计算

在计算WPL值的时候一般是用叶子节点的权值乘上其路径长度,但是实际上在构建哈夫曼树的过程中我们其实已经计算过路径长度了,即

WPL = 哈夫曼树中所有非叶子结点的权值之和

举个例子:构造 1 2 2 5 9的哈夫曼树并计算其WPL值。
在这里插入图片描述
上图即为构建出来的HuffmanTree,
WPL= (1+2)* 4 + 3 * 2 + 5 * 2 + 9 =37
这个是使用权值乘以路径长度,但是在计算其中非叶子节点的权值时,3 包含了1个(1+2),5是用3得来的,所以也包含了一个(1+2),同理10和19也是一样,所以如果直接将所有非叶子节点相加
WPL = 3 + 5 + 10 + 19 = 37。
得到的结果是一样的。这在实际计算中可以省去再次迭代计算路径长度的logn的复杂度。


实例:北京邮电大学复试题 哈夫曼树

时间限制:1秒 空间限制:65536K 热度指数:4772
校招时部分企业笔试将禁止编程题跳出页面,为提前适应,练习时请使用在线自测,而非本地IDE。

题目描述

哈夫曼树,第一行输入一个数n,表示叶结点的个数。需要用这些叶结点生成哈夫曼树,根据哈夫曼树的概念,这些结点有权值,即weight,题目需要输出所有结点的值与权值的乘积之和。

输入描述:

 

输入有多组数据。 每组第一行输入一个数n,接着输入n个叶节点(叶节点权值不超过100,2<=n<=1000)。

输出描述:

 

输出权值。

输入例子:
 

5
1 2 2 5 9


输出例子:
 

37

示例1

输入

 
   

5
1 2 2 5 9

输出

 

37

#include <iostream>
#include <queue>
#include <stdlib.h>
#include <stdio.h>
#include <map>
#include <string>
#include <cstdlib>
#include <stack>
#include <vector>
#include <math.h>
#include <algorithm>
#include <typeinfo>
#include <cstring>


using namespace std;

const int maxn = 1003;
int data[maxn];

priority_queue<int ,vector<int> ,greater<int>> q;


int main(int argc, char const *argv[])
{
	int n;
	while(cin>>n){
		while(!q.empty())	q.pop();
		for(int i=0;i<n;i++){
			cin>>data[i];
			q.push(data[i]);
		}
		int ans=0;
		while(q.size()>=2){
			int a=q.top();q.pop();
			int b=q.top();q.pop();
			q.push(a+b);
			ans+=(a+b);
		}
		cout<<ans<<endl;
	}
	return 0;
}

  • 49
    点赞
  • 135
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
1. 实验目的与要求 运用C++编写程序,解决哈夫曼编码问题。 2. 实验内容 哈夫曼树定义: 设二叉树共有n个端点,从二叉树第k个端点到树的根结点的路径长度l(k)为该端结点( 或叶子)的祖先数,即该叶子的层数减1。同时,每一个结点都带一个权(实数),第k 个端点所带权为w(k)。定义各个端结点的路径长l(k)与该点的权w(k)的乘积之和为该二 叉树的带权路径长。 哈夫曼树:对n个权w(1),w(2),…,w(n),构造出所有由n个分别带这些权的叶结点 组成的二叉树,其中带权路径长wpl最小的二叉树称为哈夫曼树。 三.算法描述及实验步骤 哈夫曼算法 哈夫曼给出一个贪心策略的算法,称为哈夫曼算法。 1) 根据给定的n个权{w(1),w(2),…,w(n)}构成n棵二叉树的森林F=(T1,…,Tn)。其中每棵二 叉树中只有一个带权为w(k)的根结点,其左右子树为空。 2) 在F中选取两棵结点的权最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树 的根结点的权为其左右子树上结点权之和。 3) 在F中删除这两棵树,并把新得的二叉树加入F中。 4) 重复以上 2),3),直到F只含一棵树为止。这棵树即为哈夫曼树。 贪心算法设计 (1) 数据结构 数组w:各字符频率或结点权,操作中可改变;w(k)为第k个字符或结点的频率。 数组u:各字符频率或结点权,不参与操作。 数组b:各字符或结点的顺序号,如b[2]=4,即排序的第2位为第4位字符(结点)。 数组lc:各字符或结点的左子号。 数组rc:各字符或结点的右子号。 数组p:各字符或结点的双亲号。 数组q:计算各字符或结点的编码的十进制。 数组f:计算各字符或结点的的二进制位数。 数组g:各字符或结点的编码十进制转换的二进制数码。 (2) 构建哈夫曼树 设置n-1次操作的k(1~n- 1)循环。对每一个k,设置j(1或2)循环,比较产生须去掉的两个元素号b[2*k- 2+j](每产生一个,该元素置非常大,以避免干扰后面比较)。产生一个新的结点:u[n+ k]=u[b[2*k-1]]+u[b[2*k]];标注该结点的左右后继与双亲地址Lc[n+k]=b[2*k-1]; rc[n+k]=b[2*k; P[b[2*k-1]]=n+k; p[b[2*k]]=n+k; 至u[2*n- 1]产生后结束构建哈夫曼树。 (3) 编码数运算 根据左边标注"0"右边标注"1"的规则,设置k(2n- 1~n+1)循环逐级反推,前一级的数须乘2,即q[lc[k]]=q[k]*2+0; q[rc[k]]=q[k]*2+1;f[rc[k]]=f[lc[k]]=f[k]+1; 为了输出q[k](k=1,2,…,n)的哈夫曼编码,应用g数组存储分离q[k]的各个二进制数字g [i](i=1,2,…,j)输出时先输出f[k]-j个前置"0",然后输出q[i](i=j,j-1,…,1) 四. 程序设计 #include <stdio.h> void main() { int a,k,i,j,n,w[100],u[100],b[100],lc[100],rc[100]; int f[100],g[100],p[100],q[100]; printf(" 请输入字符个数n:"); scanf("%d",&n); for(k=1;k<=n;k++) {printf(" 请输入第%d个字符频率: ",k); scanf("%d",&w[k]);u[k]=w[k]; } for(k=1;k<=2*n;k++) {p[k]=lc[k]=rc[k]=0;q[k]=0;} printf(" 原始频率为: "); for(j=1;j<=n;j++) printf("%d ",w[j]); for(k=1;k<=n-1;k++) {for(j=1;j<=2;j++) {b[2*k-2+j]=1; for(i=2;i<=n+k-1;i++) if(w[i]<w[b[2*k-2+j]]) b[2*k-2+j]=i; w[b[2*k-2+j]]=20000+k+j; } u[n+k]=u[b[2*k-1]]+u[b[2*k]]; w[n+k]=u[n+k]; lc[n+k]=b[2*k-1];rc[n+k]=b[2*k]; p[b[2*k-1]]=n+k;p[b[2*k]]=n+k; } printf("\n k da p lc rc\n"); for(k=1;k<=2*n-1;k++) printf("%4d%4d%4d%4d%4d\n",k,u[k],p[k],lc[k],rc[k]); q[2*n-1]=0;f[2*n-1]=0; for(k=2*n-1;k>n;k--) { q[lc[k]]=q[k]*2+0; q
5.1 数的逻辑结构 5.1.1 1、树的定义 在树中常常将数据元素称为结点 (1)有且仅有一个特定的称为根的结点; (2)当n>1时,除根结点之外的其余结点被分成m(m>0)个互不相交的有限集合T1,T2,•••Tm,其中每个集合又是一棵树,并称为这个节点的子树。 2、树的基本术语: 结点的度、树的度 叶子节点、分支结点 孩子节点、分支结点、兄弟节点 路径、路径长度 祖先、子孙 结点的层数、树的深度(高度) 层序编号 有序树、无序树 森林 5.1.2 树的抽象数据类型定义 5.1.3树的遍历操作 1、前序遍历 树的前序遍历操作定义为: 若树为空,则空操作返回;否则 (1)访问根结点 (2)按照从左向右的顺序前序遍历根结点的每一棵子树。 2、中序遍历 树的中序遍历操作定义为: 若树为空,则空操作返回;否则 (1)按照从左向右的顺序后序遍历根结点的每一棵子树; (2)访问根结点。 3、层序遍历 树的层序遍历也称作树的广泛遍历,其操作定义为树的第一层开始,自上而下逐层遍历,在同一层中,按从左向右的顺序对结点逐个访问。 5.2树的存储结构 5.2.1 双亲表示法 由树的定义可知,树中每个结点都有且仅有一个双亲结点。所以利用这一特性,可以用一维数组来存储各个结点,数组中一个元素对应一个结点,数组元素包括树中结点的数据信息以及该结点的双亲在数组中的下标。 其中: Data为数据域,存储树中结点的数据信息; Parent为指针即游标,存储该结点的双亲在数组中的小标。 5.2.2孩子表示法 1、多重链表表示法 (1)指针域的个数等于该结点的度。 (2)指针域的个数等于树的度。 2、孩子链表表示法 把孩子看成一个线性表,且以单链表存储,称为该结点的孩子链表。则n个结点有n个孩子链表。 孩子节点有两类:孩子节点、表头结点。 5.2.3 双亲孩子表示法 即将双亲表示法和孩子链表表示法相结合的存储方法。仍将各结点的孩子分别组成单链表,同时用一维数组顺序存储树中的各结点,数组元素除了包括结点的数据信息和该结点的孩子链表的头指针之外,还增设一个域存储该结点的双亲在数组的下标。 5.2.4孩子兄弟表示法 又称二链表表示法,其方法是链表中每个结点除数据域外,还设置了两个指针分别指向该结点的第一个孩子和右兄弟链表的结构: Firstchild data rightsib 指针域,存储第一个孩子结点的存储地址 数据域,存储该结点的数据信息 指针域,存储该结点右兄弟结点的存储地址 5.3二叉树的逻辑结构 最简单的树结构,特别适合计算机处理,而且任何数都可以简单的转换为二叉树。(重点内容) 5.3.1二叉树的定义 二叉树是n(n>=0)个结点的有限集合,该集合或者为空集,或者有一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成。 二叉树具有五种基本形态: 1、空二叉树; 2、只有一个根结点; 3、根结点只有左子树; 4、根结点只有右子树; 5、根结点既有左子树又有右子树 特殊二叉树: 1、斜树; 2、满二叉树; 3、完全二叉树; 5.3.2二叉树的基本性质 性质5-1 二叉树的第i层上最多有2^(i-1)个结点(i>=1)。 性质5-2 在一棵深度为k的二叉树中,最多有2^k-1个结点,最少有k个结点。 性质5-3 在一棵二叉树中,如果叶子结点的个数为n0,度为2的结点个数为n2,则n0=n2+1. 性质5-4 具有n个结点的完全二叉树的深度为【log2^n】+1。 性质5-5 对一棵具有n个结点的完全二叉树中的结点从一开始按层序编号,则对于任意的编号为i(1<=i<=n)的结点,有: (1)如果i>1,则结点i的双亲的编号为【i/2】;否则结点i是根结点,无双亲。 (2)如果2i<=n,则 结点i的左孩子的编号为2i;否则结点i无左孩子。 (3)如果2i+1<=n,则结点i的右孩子的编号为2i+1,否则结点i无右孩子。 5.3.3 二叉树的抽象数据类型定义 同树类似,在不同的应用中,二叉树的基本操作不尽相同。 5.3.4 二叉树的遍历操作 二叉树的遍历是指从根节点出发,按照某种次序访问二叉树是所有结点,使得每个结点被访问一次且仅被访问一次。由于二叉树中每个结点都可能有两个子树,因此需要寻找一条合适的搜索路径。 1、前序遍历 前序遍历二叉树操作定义为: 若树为空,则空操作返回;否则 (1)访问根结点 (2)前序遍历根结点的左子树 (3)前序遍历根结点的右子树 2、中序遍历 中序遍历二叉树操作定义为: 若树为空,则空操作返回;否则 (1)中序遍历根结点的左子树 (2)访问根结点 (3)中序遍历根结点的右子树 3、后序遍历 后序遍历根结点的左子树 后序遍历根结点的右子树 访问根结点 4、层序遍历 二叉树的层序遍历是指从二叉树的第一层开始,从上之下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。 5.4 二叉树存储结构及实现 5.4.1 顺序存储结构 具体步骤: (1)将二叉树按完全二叉树编号。 (2)将二叉树中的结点一编号顺序存储到一维数组中。 5.4.2 二叉链表 基本思想: 令二叉树的每个结点对应一个链表结点,链表结点除了存放于二叉树结点有关的数据信息外,还要设置指示左右孩子的指针。 5.4.3 三叉链表 在二叉链表存储方式下,从某个结点出发可以直接访问它的孩子结点,但要找到它的双亲结点,则需要从根节点开始搜索,最坏的情况下,需要遍历整个二叉链表。此时采用三叉树链表储存二叉树。 其中,data,lchild,rchild三个域的含义同二叉树,parent域为指向该结点的双亲结点指针。 5.4.4 线索链表 按照某种遍历次序对二叉树进行遍历,可以把二叉树中所有结点排成一个线性序列。在集体应用中,有时需要访问二叉树中的结点在某种遍历序列中前驱和后继,此时,在存储结构中应该保存结点在某种遍历序列中的前驱和后继信息。 前驱和后继结点的指针称为线索,加上线索的二叉树称为线索二叉树,加上线索的二叉链表称为线索链表。 5.5 二叉树遍历的非递归算法 5.5.1 前序遍历非递归算法 关键:在前序遍历过某个左子树后,如何找到该结点的右子树的根指针。 一般的前序遍历执行过程中,设要遍历二叉树的根指针为bt,可能出现两种情况: (1)若bt!=NULL,则表明当前二叉树不为空,此时,应输入根结点bt的并将bt保存到栈中,准备继续遍历bt的左子树。 (2)若bt=NULL,则表明以bt为根指针的二叉树遍历完毕,并且bt是栈顶指针所指结点的左子树,若栈不空,则应根据栈顶指针所指结点找到待遍历右子树的根指针并赋予bt,以继续遍历下去;若栈空,则表明整个二叉树遍历完毕。 5.5.2 中序遍历非递归算法 此算法只是需要将前序遍历的非递归算法中输出的语句cout<<bt->data移到bt=s[top--]之后即可。 5.5.3 后序遍历非递归算法 后序遍历的不同在于:结点要出入两次栈,出两次栈,这种情况的含义和处理方法为: (1)第一次出栈:只遍历晚左子树,右子树尚未遍历,则该结点不出栈,利用栈顶结点找到它的右子树,准备遍历它的右子树。 (2)第二次出栈:遍历完右子树,该结点出栈,并访问它。 设根指针为bt,则可能有以下两种情况: (1)若bt!=NULL,则bt及标志flag入栈,遍历其左子树。 (2)若bt=NULL,此时栈空,则整个遍历结束;若栈不空,则表明栈顶结点的左子树或右子树已遍历结束。若栈顶点的标志flag=1,则表明栈结点的左子树已遍历完毕,将flag修改为2,修改为2,并遍历栈定点的右子树;若栈顶结点的标志flag=2,则表明栈结点的右子树也遍历完毕,输出栈顶结点。 5.6 树、森林与二叉树的转换 1.树转换为二叉树 将一棵树转换为二叉树的方法为: (1)加线——树中所有相邻的兄弟结点之间加一条线; (2)去线——对树中的每个节点,只保留它与第一个孩子结点之间的连线,删去它与其他孩子结点之间的连线。 (3)层次调节——以根结点为轴心,将树顺时针转动一定角度,使之层次分明。 2.森林转换成二叉树 (1)将森林中的每一棵二叉树转化成二叉树; (2)从第二课二叉树开始,依次把后一棵二叉树的根结点作为一棵二叉树根节点的右孩子,当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。 3、二叉树转换为树或森林 (1)加线——若某个结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……,都与结点y用线连起来; (2)去线——删去原二叉树中所有的双亲结点与右孩子结点的连线; (3)层次调整——整理由(1)、(2)两步所得到的树或森林,使之层次分明。 (4)森林的遍历 两种遍历方法;前序遍历后续遍历。 5.7 应用举例 5.7.1 二叉树的应用举例——哈夫曼及哈夫曼编码 1、哈夫曼树也称最优二叉树,在实际中有着广泛的应用。 叶子节点的权 是对叶子结点赋予的一个有意义的数量。 二叉树的带权路径长度 设二叉树具有n个带权的叶子节点,从根节点到叶子节点的路径长度与相应的叶子节点权的乘积之和叫做二叉树的带权路径长度,记为: WPL=EWkLk 哈夫曼树 给定一组具有确定权的叶子结点,可以构造出不同的二叉树,将其中带权路径长度最小的二叉树称为哈夫曼树。 哈夫曼算法基本思想: (1)初始化:由给定的n个权构造n棵只有一个根结点的二叉树,从而得到一个二叉树集合。 (2)选取与合并:在F中选取根结点的权最小的两棵二叉树分别作为左、右子树构造一棵新的二叉树,这棵新的二叉树的根结点的权为其左右子树根结点的权之和。 (3)删除与加入:在F中删除作为左、右子树的两棵二叉树,并将新建的二叉树加入到F中。 (4)重复(2)(3)两步的操作,当集合F只剩下一棵二叉树时这棵二叉树便是哈夫曼树。 2、哈夫曼编码 在进行程序设计时,通常给每一个字符记一个单独的代码来表示一组字符,我们称之为编码。
1. 一棵二叉树的顺序存储情况如下: 树中,度为2的结点数为( )。 A.1 B.2 C.3 D.4 2. 一棵“完全二叉树”结点数为25,高度为( )。 A.4 B.5 C.6 D.不确定 3.下列说法中,( )是正确的。 A. 二叉树就是度为2的树 B. 二叉树中不存在度大于2的结点 C. 二叉树是有序树 D. 二叉树中每个结点的度均为2 4.一棵二叉树的前序遍历序列为ABCDEFG,它的中序遍历序列可能是( )。 A. CABDEFG B. BCDAEFG C. DACEFBG D. ADBCFEG 5.线索二叉树中的线索指的是( )。 A.左孩子 B.遍历 C.指针 D.标志 6. 建立线索二叉树的目的是( )。 A. 方便查找某结点的前驱或后继 B. 方便二叉树的插入与删除 C. 方便查找某结点的双亲 D. 使二叉树的遍历结果唯一 7. 有abc三个结点的右单枝二叉树的顺序存储结构应该用( )示意。 A. a b c B. a b ^ c C. a b ^ ^ c D. a ^ b ^ ^ ^ c 8. 一颗有2046个结点的完全二叉树的第10层上共有( )个结点。 A. 511 B. 512 C. 1023 D. 1024 9. 一棵完全二叉树一定是一棵( )。 A. 平衡二叉树 B. 二叉排序树 C. 堆 D. 哈夫曼树 10.某二叉树的中序遍历序列和后序遍历序列正好相反,则该二叉树一定是( )的二叉树。 A.空或只有一个结点 B.高度等于其结点数 C.任一结点无左孩子 D.任一结点无右孩子 11.一棵二叉树的顺序存储情况如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A B C D E 0 F 0 0 G H 0 0 0 X 结点D的左孩子结点为( )。 A.E B.C C.F D.没有 12.一棵“完全二叉树”结点数为25,高度为( )。 A.4 B.5 C.6 D.不确定 二、填空题(每空3分,共18分)。 1. 树的路径长度:是从树根到每个结点的路径长度之和。对结点数相同的树来说,路径长度最短的是 完全 二叉树。 2. 在有n个叶子结点的哈夫曼树中,总结点数是 2n-1 。 3. 在有n个结点的二叉链表中,为非空的链域的个数为 n-1 。 4. 某二叉树的中序遍历序列和后序遍历序列正好相反,则该二叉树一定是 任一结点无左孩子 的二叉树。 5. 深度为 k 的二叉树最多有 个结点,最少有 k 个结点。 三、综合题(共58分)。 1. 假定字符集{a,b,c,d,e,f }中的字符在电码中出现的次数如下: 字符 a b c d e f 频度 9 12 20 23 15 5 构造一棵哈夫曼树(6分),给出每个字符的哈夫曼编码(4分),并计算哈夫曼树的加权路径长度WPL(2分)。 (符合WPL最小的均为哈夫曼树,答案不唯一) 哈夫曼编码: 2. 假设用于通信的电文由字符集{a,b,c,d,e,f,g}中的字符构成,它们在电文中出现的频率分别为{0.31,0.16,0.10,0.08,0.11,0.20,0.04}。要求: (1)为这7个字符设计哈夫曼树(6分)。 (2)据此哈夫曼树设计哈夫曼编码(4分)。 (3)假设电文的长度为100字符,使用哈夫曼编码比使用3位二进制数等长编码使电文总长压缩多少?(4分) (1) 为这7个字符设计哈夫曼树为(符合WPL最小的均为哈夫曼树,答案不唯一): (2) 哈夫曼编码为: a:01;b:001;c:100;d:0001;e:101;f:11;g:0000 (3) 假设电文的长度为100字符,使用哈夫曼编码比使用3位二进制数等长编码使电文总长压缩多少? 采用等长码,100个字符需要300位二进制数,采用哈夫曼编码发送这100个字符需要261二进制位,压缩了300-261=39个字符。压缩比为39/300=13%。 3. 二叉数T的(双亲到孩子的)边集为: { <A,B>, <A,C>, <D,A>, <D,E>, <E,F>, <F,G> } 请回答下列问题: (1)T的根结点(2分): (2)T的叶结点(2分): (3)T的深度(2分): (4)如果上述列出边集中,某个结点只有一个孩子时,均为其左孩子;某个结点有两个孩子时,则先列出了连接左孩子的边后列出了连接右孩子的边。画出该二叉树其及前序线索(6分)。 (1)T的根结点 (2)T的叶结点 : (3)T的深度 : (4)该二叉树其及前序线索为: 4.现有以下按前序和中序遍历二叉树的结果: 前序:ABCEDFGHI 中序:CEBGFHDAI 画出该二叉树的逻辑结构图(5分),并在图中加入中序线索(5分)。 5.有电文:ABCDBCDCBDDBACBCCFCDBBBEBB。 用Huffman树构造电文中每一字符的最优通讯编码。画出构造的哈夫曼树,并给出每个字符的哈夫曼编码方案。(符合WPL最小的均为哈夫曼树,答案不唯一) (1)构造哈夫曼树(6分): (2)哈夫曼编码方案(4分):

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值