1、1,第六章 树,数据结构,2,定义说明了: 树是一种递归的数据结构树中包含树。 结构特点:结点间有明显层次关系。,一. 树的定义树是n(n0)个结点的有限集,n=0 空树; 在一棵非空树中: (1)有且只有一个特定的结点根结点(root); (2)当n1,其余结点可分为m个互不相交的子集T1,T2, ,Tm,它们又是树 根结点的子树(Sub Tree)。,6.1 树的结构定义和基本术语,例如:学校的行政关系、书的层次结构、人类的家族血缘关系、操作系统的目录、结构程序模块等。,3,A是根 T1 = B,E,F T2 = C,G,I,J T3 = D,H 在树中,每个结点被定义为它的每个子树的根结
2、点的前驱。,二. 树的表示,(1) 结点连线 隐含:上方结点是下方结点的前驱。,4,(2) 二元组表示 如下面的树可表示为:,K=C,G,I,J R=,,5,(3) 集合图表示法,每棵树对应一个圆形,圆内包含根结点和子树。,6,(4) 凹入表示法,每棵树的根对应着一个条形,子树的根对应着一个较短的条形。,7,(5) 广义表表示,每棵树的根作为由子树构成的表的名字而放在表的左边。,A ( B (E,F),C (G(I,J) ),D (H) ),(1)树的结点:包含一个数据元素及若干指向其子树的分支 (2)结点的度(degree):结点拥有的子树数。 (3)根结点:无前驱。 (4)叶子(终端结点)
3、:度为零的结点。无后继。 (5)分支结点(非终端结点) 度不为零的结点。除根结点外,分支结点也称内部结点(1个前驱,多个后继) (6)树的度:树内各结点的度的最大值。,三.基本术语,9,(7)孩子:结点的子树的根称为该结点的孩子。相应地,该结点称为孩子的双亲。 (8)兄弟:同一双亲的孩子之间互为兄弟。 (9)祖先:结点的祖先是从根到该结点所经分支上的所有结点。 (10)子孙:以某结点为根的子树中的任一结点都称为该结点的子孙。,10,(11)结点的层次:根为第一层,根的孩子为第二层。 (12)树的深度(高度):树中结点的最大层次。 (13)有序树及无序树: 如:,是两棵不同的有序树。,任何一棵非
4、空树是一个二元组Tree = (root,F ) 其中:root 被称为根结点,F 被称为子树森林,(14) 森林:,是 m(m0)棵互 不相交的树的集合,A,root,F,12,四. 树的基本操作,(1) 初始化空树: INITIATE(以结点x为根,以F为子树森林构造树T。,设:Tree T,13,(7) INS_TREE(,由于树和二叉树可以相互转换,先讨论二叉树,14,二叉树或为空树;或是由一个根结点加上两棵分别称为左子树和右子树的、互不交的二叉树组成。,根结点,左子树,右子树,E,F,特点:每个结点至多只有两棵子树,子树有左右之分,其次序不能任意颠倒。,6.2 二叉树,6.2.1 二
5、叉树的定义,15,二叉树的五种基本形态:,N,空树,只含根结点,N,N,N,L,R,R,右子树为空树,L,左子树为空树,左右子树均不为空树,16,(1) 一棵度为2的树与一棵二叉树是否有区别? (2) 有三个结点的二叉树与有三个结点的树的形态是否相同?,三个结点的树: 三个结点的二叉树,问题:,17,特殊的二叉树:,(2) 完全二叉树:除最后一层外, 其余各层都是满的;最后一层或者是满的,或者是右边缺少连续的若干结点。,(1)满二叉树: 二叉树中每一层的结点数都达到最大,18,理想平衡树:在一棵二叉树中,若除最后一层外,其余层都是满的,则称此树是理想平衡树。 满二叉树和完全二叉树是理想平衡树。
6、但理想平衡树不一定是完全二叉树。,19,性质1,在二叉树的第i层上至多有2i-1个结点(i1)。 用归纳法证明: (1)i=1时, 2i-1=1,即只有一个根结点。 (2)设j=i-1时命题成立,证明j=i时命题也成立。 设:第i-1层上至多有2i-2个结点, 在第i层上的结点数最多为:,6.2.2 二叉树的性质,考虑:m叉树的第i层至多有 个结点。,mi-1,(因为二叉树中每个结点的度至多为2),2i-2*2= 2i-1,20,深度为k的二叉树至多有2k-1个结点,(k1)。,性质2,= ( 1 + 2 + 22 + + 2k-1) = 2k-1,(第i层上的最多结点数目),考虑: 深度为k
7、的m叉树,最多有 个结点 。,对任何一棵二叉树T,如果其叶子结点数目为n0,度为2的结点数目为n2,则有n0=n2+1。,性质3,证明: 设度为1的结点个数为n1, 总结点数为n,则有n = n0 + n1 + n2 设分支数为B, 从下向上看,知: B = n - 1 从上向下看,这些分支数是由度为1和度为2的结点发出的, 因此: B = n1 + 2n2n0 + n1 + n2 = n1 + 2n2 + 1 所以: n0 = n2 + 1,22,性质4,具有n个结点的完全二叉树的深度为:k = log2n + 1。,2k-1-1 n 2k-1,证明:设深度为k,由性质2,最多结点数目为2k
8、-1, 深度为k-1的最多结点数目为2k-1-1。,k-1 log2n k,即 :2k-1 n 2k,有n个结点的理想平衡二叉树的深度为:k = log2n + 1,考虑:具有n个结点的m叉树的最小深度?, k-1 = log2n,23,如果对一棵有n个结点的完全二叉树(其深度为log2n + 1)的结点按层序编号,则对任一结点i(1in),有 (1)如果i = 1, 则结点i是根。如果i1, 则其双亲parent(i)是结点i/2 (2)如果2in,则结点i 为叶子,否则其左孩子Lchild(i)是结点2i。 (3)如果2i+1n, 则结点i无右孩子,否则其右孩子是结点2i+1。,性质5,2
9、4,证明(2),(3): 对于i=1,左孩子编号是2, 右孩子编号是3。 对于i1,可分两种情况讨论: 设第i号结点的 左孩子编号为2i;右孩子编号为2i+1; 第一种情况:第i号结点和第i+1号在同一层。第二种情况:第i号结点和第i+1号不在同一层 则编号为i的结点必为第j层的最后一个结点,那么编号为i+1的结点则为第j+1层的第一个结点。,25,对于完全二叉树,可用一维数组来存储。,6.2.3 二叉树的存储结构,一、顺序存储结构,例如: char btree11;,26,对于一般二叉树,则必须将其“完全化”之后来存储。,27,char btr12,优点:直接利用元素在数组中的位置(下标)表
10、示其逻辑关系。缺点:若不是完全二叉树,则会浪费空间。因此,顺序存储适合于(接近)完全二叉树。,28,结点的结构,在含有n个结点的二叉链表中有多少空指针域?,性质,在含有n个结点的二叉链表中有n+1个空指针域。,二、二叉链表存储结构,29,root,三叉链表,结点结构:,30,二叉链表类型定义:typedef struct BiTNodeDataType data;struct BiTNode *lchild;struct BiTNode *rchild; BiTNode,*BiTree; BiTree bt; 三叉链表的定义:BiTNode中加一个parent成员;,31,静态二叉链表: 则结
11、点类型可定义为: typedef struct BiNodeDataType data;int lchild;int rchild; StaticBiNode; typedef StaticBiNode StaticBiListM;,32,33,根结点(D) 非空二叉树 左子树(L)右子树(R) 共有六种访问次序:DLR, LDR, LRD, DRL, RDL, RLD 若限定先访问左子树,后访问右子树,则只有: DLR(先序遍历), LDR(中序遍历), LRD (后序遍历)。,6.3 遍历二叉树和线索二叉树,6.3.1 遍历二叉树,一. 递归定义和递归算法,(1)DLR 先序遍历(先根遍历
12、)if(树不空) 访问根结点; 先序遍历左子树; 先序遍历右子树;,(2)LDR 中序遍历(中根遍历)if(树不空) 中序遍历左子树;访问根结点;中序遍历右子树;,(3)LRD 后序遍历(后根遍历)if(树不空) 后序遍历左子树; 后序遍历右子树;访问根结点;,35,递归算法: (1) 先序遍历void preorder(BiTree bt)if(bt) printf(btdata);preorder(btlchild);preorder(btrchild); / preorder,36,(2) 中序遍历void inorder(BiTree bt)if (bt) inorder(btlchi
13、ld);printf(btdata);inorder(btrchild); / inorder,37,(3) 后序遍历void postorder(BiTree bt)if(bt) postorder(btlchild);postorder(btrchild);printf(btdata); / postorder,38,先序遍历:D L R,D L R,ABDC,先序序列:- + A * B C D / E F 中序序列:A + B * C D E / F 后序序列:A B C D - * + E F / - 层次遍历:- + / A * E F B C D,mid(a lch)mid(*l
14、ch);printf(a); mid(a rch);mid(-lch); printf(*); mid(b lch);mid(*rch);printf(b); mid(b rch); mid(bt); printf(-); (bt=-)mid(c lch);mid(-rch); printf(c); mid(c rch);,中序遍历:a * b - c,a,b,*,c,41,1. 已知先序序列和中序序列,可唯一确定一棵二叉树。 若ABC是二叉树的先序序列,可画出5棵不同的二叉树;,问题:由已知遍历序列,如何求二叉树?,二. 由二叉树的编历序列求二叉树,若ABCD是二叉树的先序序列, 可画出14
15、棵不同二叉树。 一但加上二叉树的中序序列,可唯一确定一棵二叉树。先序序列: D L R中序序列: L D R 在先序序列中确定根,在中序序列中分左右子树,2.由后序序列和中序序列能确定唯一的二叉树。 3.由先序序列和后序序列不能唯一确定一棵二叉树。,B D G H,E C I F,43,P=NULL,(1) 先序遍历: 有三件事要顺序完成: 访问根结点X;遍历XL;遍历XR 其中连接没问题,但和的连接有问题。为了将来遍历XR ,必须将 Xrchild的值存起来; 用指针栈来存储。,&A,&C,p=NULL,p=NULL,&D,栈S,三. 二叉树遍历的非递归算法,void preorder(Bi
16、Tree bt )/顺序栈s用于存放指针InitStack(s); push(s,bt); while (!StackEmpty(s) p = pop(s);while(p) printf(pdata); if (prchild) push(s,prchild);p= p lchild; / preorder,44,(2) 中序遍历,P=NULL,设指针p指向当前遍历的二叉树的根,则若p不空,令p指向其左子树的根,即p=plchild,对左子树进行中序遍历,此时若p仍不空,再令p=plchild,如此重复直至p=NULL为止。因空树的遍历为空操作,左子树遍历完成。接着应访问根结点。,顺序完成三
17、件事: 遍历XL;访问根结点X;遍历XR,用栈记下p所走过的所有路经。,45,算法描述 :(即工作栈的状态) (1) 首先指向根结点的指针进栈。 (2) 当栈顶指针不是空指针时,则栈顶指针的左孩子指针进栈,即准备遍历左子树; (3) 若栈顶指针为空指针,则空指针出栈,之后若栈不空,则栈顶元素出栈,访问出栈指针所指的结点。 (4) 将刚访问过的结点的右孩子指针作为新的根继续(1)的工作,直到栈空。,void inorder (BiTree bt ) /非递归中序遍历根指针为bt的二叉树,栈s存放指针InitStack(s); push(s,bt);while (! StackEmpty(s) w
18、hile(GetTop(s) NULL)push(s,GetTop(s)lchild);p = pop(s); / 空指针取出if (!StackEmpty(s) p= pop(s); printf(pdata);push(s,prchild) ; / inorder,47,InitStack(s); push(s,bt); while (!StackEmpty(s) while(GetTop(s)NULL)push(s,GetTop(s)lchild); p=pop(s); / 空指针取出 if (!StackEmpty(s) p=pop(s);printf(pdata);push(s, p
19、rchild); ,栈s,输出:,&-,&*,&a,NULL,p-,a,NULL,p-,*,&b,NULL,p-,b,NULL,p-,-,&c,NULL,p-,c,NULL,栈s,48,void layer(BiTree bt)InitQueue(Q);if (bt) addqueue(Q,bt); while(! QueueEmpty(Q) p=delqueue(Q); printf(pdata);if (plchild) addqueue(Q,plchild );if (prchild) addqueue(Q,prchild ) ,(3) 层次遍历:,需要一个队列保存将要访问的每一个结点的
20、指针,A,B,C,G,D,F,E,H,I,(1) 读入根结点的值; (2) 如果值为空结点标志, 则根指针置为空指针; 否则: 生成根结点;(申请空间,装入根结点的值); 用同样的方法生成左子树; 用同样的方法生成右子树; ,void crt_tree(BiTree ,按下列顺序读入字符: ABC# #DE#G# # F # # #,1. 读入结点值的递归算法按先根遍历顺序读入每个结点值建立二叉链表,四. 建立二叉树的链式存储结构,CRT(bt); (ch=A),new(bt); A 存入bt;,CRT(btlch); (ch=B),new(btlch); B存入btlch;,CRT(Blch
21、); (ch=#),读入序列: A B # C # # #,Blch=NULL,CRT(Brch); (ch=C),new(Brch); C存入Brch;,CRT(Clch); (ch=#),CRT(Crch); (ch=#),CRT(btrch); (ch=#),A,B,#,C,#,#,#,51,2. 读入边的非递归算法,按从上到下、从左到右的顺序依次输入二叉树的边(区分左右分支)。即读入 (father,child, lrflag)为一条边的信息,建立二叉树的二叉链表。,需要一个队列保存已建好的结点的指针。 (双亲结点指针),算法核心: 每读一条边之后,生成孩子结点,并做为叶子结点;之后将
22、该结点的指针保存在队列中。从队头找该结点的双亲结点指针,按lrflag值建立双亲结点的左右孩子关系。,52,(#, A, 0) (A, B, 0) (B, C, 1) (C, D, 0) (C, E, 1) (D, F, 0) (D, G, 1) (F, #, 0),(#, A, 0),(A, B, 0),(B, C, 1),(C, D, 0),(C, E, 1),(D, F, 0),(D, G, 1),(F, #, 0),A,B,C,D,E,G,F,队列中保存已建好的(孩子)结点的指针,读入方式:,bt=NULL,bt,53,void Creat_BiTree(BiTree &bt) Ini
23、tQueue(Q);bt=NULL;scanf(fa, ch, lrflag); while (ch#)p= (BiTree)malloc(sizeof(BiTNode) ; pdata=ch; / 创建孩子结点 plchild= prchild =NULL; / 做成叶子结点 addqueue(Q,p); /指针入队列if (fa = # ) bt= p /所建为根结点 else /非根结点的情况scanf(fa, ch, lrflag); / end_while / Create_BiTree,54,s=GetHead(Q); /取队列头元素(指针值) while (sdata fa )
24、delqueue(Q);s=GetHead(Q); / 在队列中找到双亲结点 if (lrflag=0) slchild = p; / 链接左孩子结点 else srchile = p; / 链接右孩子结点,具体规定如下: (1) 每棵树的根结点作为由子树构成的表的名字而放在表的前面; (2) 每个结点的左子树和右子树用逗号分开,没有左子树时,则逗号不能省略; (3) 表尾用一个特殊字符作为结束符;,例如:右边的二叉树的广义表为: A(B(C,D(E(,G),F)#,有五种字符:字母、(、,、)和#,3. 用广义表表示的输入法建立二叉链表,56,算法如下:循环读字符给ch,直到#。k=0,1分
25、别表示左右孩子;栈s存放指向结点的指针。 (1) 若ch是字母:则是结点,应为它建立一个新叶子结点,并把该结点(若不是根)分左右子树链接到其双亲结点; (2) 若ch是(: 表示子表开始,将上一步建立的结点指针入栈保存,设标志k=0; (3) 若ch是): 表示子表结束,应退栈; (4) 若ch是,: 表示以左孩子为根的子树处理完,应接着建立右子树,设标志k=1;,例:求二叉树的深度若二叉树为空,则它的深度为,否则,求左子树和右子树的最大深度加。 int depth(BiTree bt)if (bt=NULL) return(0);else depl=depth(btlchild);depr=
26、depth(btrchild);if(depldepr) return(depl+1);else retrun(depr+1); / depth,五. 二叉树的递归算法举例,58,利用n个结点的二叉链表中的n+1个空指针域,定义:,实现:,6.3.2 线索二叉树,前驱与后继:在二叉树的先序、中序或后序遍历序列中两个相邻的结点互称为前驱与后继; 线索:指向前驱或后继结点的指针称为线索; 线索二叉树:加上线索的二叉链表表示的二叉树叫线索二叉树; 线索化:对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化;,59,以这种结点结构构成的二叉链表,称为线索链表,为避免混淆,结点中需增加两个标志域:,
27、线索二叉树的存储 采用二叉链表,中序序列:CBEGDFA,中序线索树二叉树,中序线索树二叉链表,61,0,0,0,0,1,1,1,1,1,1,例1:先序线索二叉链表,先序序列:ABCDE,62,0,0,0,0,1,1,1,1,1,1,例2:中序线索二叉链表,中序序列:BCAED,63,0,0,0,0,1,1,1,1,1,1,例3:后序线索二叉链表,后序序列:CBEDA,遍历中序线索二叉树: 先找到遍历序列中的第一个结点,然后依次找结点的后继结点直至其后继结点为空时为止。或从最后一个结点开始依次找结点前驱直至其前驱为空时为止。 以下讨论如何在线索树中找结点的后继或前驱。设指针p指向的当前结点为x
28、,x的后继next(x)=? (1)若结点prtag=1(是线索),则next(x)=prchild; (2)若prtag=0,(即有右孩子),根据中序遍历的规律可知,next(x)应是遍历右子树时访问的第一个结点。在线索树中找结点x 的前驱prior(x)? (1) 若pltag=1, (是线索),则prior(x)= p lchild; (2)若pltag=0 ,则prior(x)应是遍历左子树时最后访问的一个结点(左子树中最右下的结点),为方便起见,仿照线性表的存储结构,在线索树上添加一个头结点。头结点定义如下: 二叉树的根作为它的左孩子(ltag=0),没有右孩子 (rtag=1),其
29、rchild指向遍历时的最后一个结点; 头结点作为遍历时最后一个结点的后继,作为遍历时第一个结点的前驱 双向循环线索链表,即: thead= (BiThrTree)malloc(sizeof(bithnode); theadlchild=thead; theadlfag=0; /不是线索 theadrchild=thead; theadrfag=1;/是线索,带头结点的空线索二叉树表示为:,中序序列: CBEGDFA,0 0 A 10 B 01 C 1 0 D 01 E 0 1 F 11 G 1,67,类型定义 typedef enum link,thread pointertag; type
30、def struct BiThNodeDataType data;pointertag ltag, rtag ;struct bithnode *lchild,*rchild; BiThNode ,*BiThrTree ;BiThrTree thead;,void inorder_thrt(BiThrTree thrt)p = thrtlchild;while(pthrt)while(pltag= =0) p = plchild;printf(p data); /访问左子树中的第一个结点while(prtag=1 & prchild thrt) p=prchild ; printf(pdata
31、) ;/访问后继p=prchild; /当p有右孩子时指向其右孩子 特点:既不用递归,也不用栈。,69,二叉树的线索化:即是将二叉链表中的空指针改为指向其前驱或后继的线索。需要对二叉树进行遍历,为了记录在遍历过程中访问结点的先后关系,附设一个指针pre始终指向刚刚访问过的结点,分两个算法实现: 算法1:已知根结点和它的前驱结点,将其线索化 算法2:生成一个头结点,调用算法1,并将结果与头结点一起线索化,生成一个带头结点的线索二叉树;,if (plchild=NULL) / 建立p的前驱线索 pltag =1;plchild = pre ; if (pre rchild = = NULL) /
32、建pre的后继线索 prertag=1; prerchild = p ; pre = p; / 保持 pre 指向 p 的前驱,void inthread(BiThrTree p) /p 为根指针,pre为bt的前驱结点的指针,中序线索化一个以 /p为根的二叉树 ,算法结束时,pre指向遍历后最后一个结点if ( p) inthread( plchild ); / 左子树线索化,inthread(prchild) /右子树线索化 /inthread,void crt_inthrt(BiThrTree &thrt,BiThrTree bt) thrt=(BiThrTree)malloc(size
33、of(bithnode) ; thrtltag =link; thrtrtag =thread;thrtrchild =thrt; / 初始化一个只有头结点的空树if(bt=NULL)thrtlchild =thrt; / 完成空树的操作elsethrtlchild =bt; pre = thrt;inthread(bt, pre); /中序遍历线索化prerchild =thrt; /最后一个结点线索化prertag = thread;thrtrchild = pre; /crt_inthrt,72,一、双亲表示法(顺序存储)用一组连续空间存储树的结点,同时在每个结点中附设一个域指示其双亲的
34、位置。 #define MAXLEN 树中结点个数 typedef struct tnodeDataType data;int parent TNode; typedef TNode TreeMAXLEN;,6.4 树和森林,6.4.1 树的存储结构,73,例:树的双亲表示法示例,typedef struct tnodeDataType data;int parent TNode ; typedef TNode TreeMAXLEN,结点序号 data parent,74,(1)由于树中每个结点可有多个孩子,则每个结点可设多个指针成员,每个指针指向一个孩子。对于度为m的树,结点结构如下:,(3
35、)将每个结点的孩子结点排列起来,连接成一个单链表。n个结点有n个孩子链表, n个孩子链表的头指针可放在数组中,称为孩子链表。,(2)每个结点有几个孩子,就有几个指针。,二、孩子表示法(链式存储),问题:会有太多的空指针!,75,孩子链表,孩子链表的类型定义: typedef struct CTNode / 表结点定义int child; / 孩子结点在头结点数组中的位置struct node *next ; /下一个孩子的位置 * ChildPtr ; typedef struct / 定义头结点DataType data;ChildPtr link ;/ 孩子链表的头指针 CTbox ; t
36、ypedef struct CTbox nodesMAXLEN;int n,r; / 结点数目和根的位置 ChildList;,带双亲的孩子链表:将双亲表示法和孩子表示法结合起来。,三、孩子兄弟表示法:又称树的二叉链表表示法 即用二叉链表作树的存储结构。结点中的两个指针分别指向该结点的第一个孩子和下一个兄弟,typedef struct CSNodeDataType data;struct CSNode *fch,*nsib ; CSNode,*CSTree;,79,树,二叉树,6.4.2 森林与二叉树的转换,从树的链表表示的定义可知,任何一棵与树对应的二叉树,其右子树必空。若把森林中第二棵树
37、的根结点看成是第一棵树的根结点的兄弟,则可导出森林和二叉树的关系。,森林 二叉树,81,森林与二叉树互相转换的方法: 一. 森林转换成二叉树 将森林中的每棵树转换为二叉树; 将第i+1棵树作为第i棵树的右子树,依次连接成一棵二叉树; 二. 二叉树转换成森林 将二叉树根的右子树作为另一棵二叉树,将新分出的二叉树转换成森林; 将二叉树转换成森林;,82,树的遍历 1. 按层次:先访问第一层的结点,之后访问第二层的结点。 2. 先根遍历:先访问根结点,依次先根遍历其各子树。 3. 后根遍历:依次后根遍历其各子树 , 再访问根结点。,6.4.3 树和森林的遍历,83,树 先根:A B E F C G
38、I J D后根: E F B I G J C D A,二叉树 先序遍历 中序遍历,84,先序遍历树的递归算法:void pre_order_tree(CSTree T)p=T;while(pNULL) printf(p data);if (pfirstchild NULL) pre_order_tree(pfirstchild);p = pnextsibling ; / preorder,85,1.森林中第一棵树的根结点;,2. 森林中第一棵树的子树森林;,3. 森林中其它树构成的森林。,可以分解成三部分:,森林,森林的遍历,86,若森林不空,则 访问森林中第一棵树的根结点; 先序遍历森林中第
39、一棵树的子树森林; 先序遍历森林中(除第一棵树之外)其余树构成的森林。,先序遍历,即:依次从左至右对森林中的每一棵树进行先根遍历。,87,中序遍历,若森林不空,则 中序遍历森林中第一棵树的子树森林; 访问森林中第一棵树的根结点; 中序遍历森林中(除第一棵树之外)其余树构成的森林。,即:依次从左至右对森林中的每一棵树进行后根遍历。,例如:,先序遍历:A B C D E F G H I J 中序遍历:B C D A F E H J I G,结论:,89,结点间的路径长度:两个结点之间的分支数。 结点的权值:附加在结点上的信息。 结点带权路径:结点上权值与该结点到根之间的路径长度的乘积。,6.5 哈
40、夫曼树及其应用,6.5.1 最优二叉树哈夫曼树,90,树的带权路径长度:(Weight Path Length) 树中所有叶子结点的带权路径长度之和。,WPL =,(叶到根的平均路径长度),哈夫曼(huffman)树:使WPL取最小值的二叉树,又称最优二叉树,例:有n个叶子结点,第 i个叶子结点的权值为Wi,根到该结点的路径长度为Li,则:,91,例如,给定权值5,4,7,2,5,可生成多棵二叉树,WPL =2*1+7*3+4*3 +5*3+5*3=65,WPL=2*1+ 4*2+5*3+7*4+5*4=73,92,WPL= (2+4)*3+(7+5+5)*2=52,WPL= (5+5+7)*
41、2+(2+4)*3=52,93,如何构成一棵最优二叉树? (哈夫曼算法) (1)根据n个权值w1, w2, , wn构成n棵二叉树的集合F=T1, T2, , Tn,其中每棵二叉树Ti只有一个带权为Wi的根结点,其左右子树均空。 (2)在F中选两棵根结点的权值最小的树作为左右子树构成一棵新的二叉树,且根结点的权值为其左右子树根结点的权值之和。 (3)在F中删除这两棵树,同时将新的二叉树加入F (4)重复(2)、(3),直到F只含一棵树为止。,9,例如: 已知权值 W= 5, 6, 2, 9, 7 ,5,6,2,7,2,5,7,6,9,7,6,7,13,9,9,9,16,29,96,哈夫曼树的应
42、用:,在解决某些判定问题时的最佳判定算法。 例: 将学生百分成绩按分数段分级的程序。 若学生成绩分布是均匀的,可用二叉树结构来实现。,输入10000个数据,则需进行28000次比较。,读入一个a值平均判断: (1+2+3+4+4)*0.2=2.8(次),97,学生成绩分布不是均匀的情况:,构造一棵哈夫曼树:,再将每一比较框的两次比较改为一次:,WPL=0.4*1+0.3*2+ 0.15*3+(0.05+0.1)*4=2.05,WPL=(0.4+0.3+0.1)*2 +(0.05+0.15)*3 =2.20,输入10000个数据,仅需进行22000次比较。,98,哈夫曼编码是二进制编码形式,用于
43、(网络)通信中,它作为一种最常用无损压缩编码方法,在数据压缩程序中具有非常重要的应用。如需传送字符串ABACCDA,只有4种字符:A、B、C、D,只需两位编码。如分别编码为00、01、10、11,上述字符串的二进制总长度为14位。在传送信息时,希望总长度尽可能短,可对每个字符进行不等长度的编码,且出现频率高的字符编码尽量短。 如A、B、C、D的编码分别为 0、00、1、01时,上述电文长度会缩短,但可能有多种译法。 如0000可能是AAAA , ABA , BB。,6.5.2 哈夫曼编码,99,若要设计不等长编码,任一字符的编码都不是另一个字符编码的前缀。这种编码称为前缀编码。,可利用二叉树来
44、设计二进制的前缀编码,将每个字符出现的频率作为权,设计一棵哈夫曼树,左分支为0,右分之为1,就得到每个叶子结点的编码。,例如:编码:0、00、1、01就不是前缀编码。,结论1,存储结构: #define N 字符数目; #define M 结点数目; / m=2n-1 huffman树用静态三叉链表做存储结构,且0号单元不用 huffman编码用指向字符的指针数组来动态管理存储;且0号单元不用,证:m=n0 +n1 +n2 ,因为:n1=0 ,n2=n0-1 ,所以: m = 2n0 1 , 即 m=2n-1 .,huffman树中没有度为 1 的结点。,结论2,有n个叶子结点的huffman
45、树中共有m=2n-1个结点。,typedef structchar data ; int weight;int parent, lch, rch ; NodeType; /huffman树结点类型,typedef char * HufCode ; /动态分配指针数组存储huffman编码,typedef NodeType HufTreeM+1; /静态三叉链表存储huffman树,例:设有4个结点a、b、c、d,权值分别为(0.4,0.3,0.1,0.2)。,data weight parent lch rch,a 0.4 0 0 0b 0.3 0 0 0c 0.1 0 0 0d 0.2 0
46、0 0,0.3 0 3 4,5,0.6 0 2 5,6,1.0 0 1 6,7,7,0 1 2 3 4 5 6 7,5,6,算法1:已知n个字符的权值,生成一棵huffman树。 void huff_tree (int wn, HufTree &ht)/ w存放权值 for(i=1;in;i+) / 哈夫曼树初始化 hti.weight=wi-1; hti.parent=0; hti.lch=0; hti.rch=0 ; htn+1m.parent=0; for(i=n+1;im;i+) /构造n-1个非叶子结点 select(i-1, s1, s2); /在ht1 i-1中选择两个双亲为0并且权值最小的两个结点, /最小结点位置为s1,次小的位置为s2 。hti.weight=hts1.weight+hts2.weighthti.lch=s1; hti.rch=s2;hts1.parent=i; hts2.parent=i; /huff_tree,