1、p树、二叉树的定义、性质和存储结构;p二叉树的遍历和线索化以及遍历算法的各种描述形式;p树遍历;p二叉树的多种应用。p熟练掌握二叉树的结构特性;p熟悉二叉树的各种存储结构的特点及适用范围;p熟练掌握二叉树各种遍历策略的递归和非递归算法,了解遍历过程中“栈”的作用和状态,而且能灵活运用遍历算法实现二叉树的其它操作;p理解二叉树线索化的实质是建立结点与其在相应序列中的前驱或后继之间的直接联系;p熟悉树的各种存储结构及其特点;p学会编写实现树的各种操作的算法;p了解最优树的特性,掌握建立最优树和哈夫曼编码的方法。p树是一类重要的非线性数据结构,是以分支关系定义的层次结构;p树是n(n0)个结点的有限
2、集,非空树满足:n有且仅有一个特定的称之为有且仅有一个特定的称之为(root)的结点;的结点;n除根以外的其余结点可分为除根以外的其余结点可分为m(0 mn)个互不相交的个互不相交的子集子集T1,T2,T3Tm,其中每个子集本身又是一棵树,其中每个子集本身又是一棵树,且且称为根的称为根的。p特点:除根结点外,每个结点都仅有一个前趋(父)结点。p其它表示方法:n嵌套集合表示法嵌套集合表示法n凹入表表示法凹入表表示法参见教材参见教材120页页AB C DE F G H IJ1层2层3层4层p结点的度(结点的度(degree)n结点所拥有的子树的数目。结点所拥有的子树的数目。p叶子结点(叶子结点(l
3、eaf-又称终端结点又称终端结点 terminal node)n度为零的结点。度为零的结点。p分支结点(分支结点(branch node)n度不为零的结点度不为零的结点p孩子(孩子(child)n结点的子树的根称为结点的孩子。结点的子树的根称为结点的孩子。p双亲(双亲(parent)n结点是其孩子的双亲。结点是其孩子的双亲。p祖先(祖先(forefather)n从树根到双亲的所有结点称为该结点的祖先。从树根到双亲的所有结点称为该结点的祖先。p子孙(子孙(progeny)n以结点为根的子树的所有结点称为该结点的子孙。以结点为根的子树的所有结点称为该结点的子孙。p兄弟(兄弟(sibling)n同一
4、父母的所有孩子互称兄弟。同一父母的所有孩子互称兄弟。p层次(层次(level)n树根为第一层,其孩子为第二层,孙子为第三层,以树根为第一层,其孩子为第二层,孙子为第三层,以此类推。此类推。p堂兄弟(堂兄弟(cousin)n双亲在同一层的结点互称堂兄弟。双亲在同一层的结点互称堂兄弟。p深度(深度(depth)n树中结点的最大层次称为树的深度。树中结点的最大层次称为树的深度。p有序树(有序树(ordered tree)n结点各子树从左至右是有秩序的树称为有序树。结点各子树从左至右是有秩序的树称为有序树。p无序树(无序树(unordered tree)n结点各子树没有秩序的树称为无序树。结点各子树没
5、有秩序的树称为无序树。p森林(森林(forest)n森林是森林是 m(m0)棵互不相交的树的集合。棵互不相交的树的集合。p初始化操作InitTree(&T)p求根函数 Root(T)p求双亲函数 Parent(T,cur_e)p求左孩子结点函数LeftChild(T,cur_e)p建树函数CreateTree(&T,definiton)p清空树函数ClearTree(&T)p插入子树函数InsertChild(&T,&p,i,c)p删除子树函数DeleteChild(&T,&p,i)p遍历函数TraverseTree(T,visit()p求右兄弟函数RightSibling(T,cur_e)p
6、定义n二叉树是二叉树是n(n 0)个结点的有限集合,它或为空个结点的有限集合,它或为空树树(n=0),或由一个根结点和两棵互不相交的左,或由一个根结点和两棵互不相交的左子树和右子树的二叉树组成。子树和右子树的二叉树组成。p二叉树的特点:n定义是递归的;定义是递归的;n0 结点的度结点的度 2;n是有序树。是有序树。p二叉树的五种基本形态p两种特殊的二叉树n满二叉树:每一层上的结点数都是最大结点数。满二叉树:每一层上的结点数都是最大结点数。n完全二叉树:只有最下面两层结点的度可小于完全二叉树:只有最下面两层结点的度可小于2,而最,而最下一层的叶结点集中在左边若干位置上。下一层的叶结点集中在左边若
7、干位置上。12 34 5 6 712 34 5 6 78 9 10p性质1n二叉树的第二叉树的第i层上至多有层上至多有2i-1(i 1)个结点。个结点。p性质2n深度为深度为k的二叉树至多有的二叉树至多有2k-1个结点个结点(k 1)。p性质3n对任何一棵二叉树对任何一棵二叉树T,如果其终端结点数为,如果其终端结点数为n0,度为,度为2的的结点数为结点数为n2,则,则n0=n2+1。p性质4n具有具有n个结点的完全二叉树的深度为个结点的完全二叉树的深度为 log2n+1。p性质5n对一棵具有对一棵具有n个结点的完全二叉树个结点的完全二叉树(其深度为其深度为 log2n+1),对其结点按层从上至
8、下对其结点按层从上至下(每层从左至右每层从左至右)进行进行0至至n-1的编的编号,则对任一结点号,则对任一结点i(0 i0,则i的双亲是(i-1)/2。p若2i+1n,则i有左孩子,左孩子是2i+1。p若2(i+1)lchild);Preorder(t-rchild);void Inorder(BiTree t)if (t)Inorder(t-lchild);visit(t);Inorder(t-rchild);void Postorder(BiTree t)if(t)Postorder(t-lchild);Postorder(t-rchild);visit(t);p利用遍历结果确定二叉树问题
9、n先序序列先序序列+中序序列中序序列n中序序列中序序列+后序序列后序序列n先序序列先序序列+后序序列后序序列(x)先序序列:ABCDEFGH 中序序列:BDCEAFHGABFCGDEH思考:层序+先序/中序/后序,能否确定?如何做?例如:层序ABCDEFGHIJ,中序DBGEHJACIF void get_post(char*pre,char*in,char*post,int n)if(n=0)return;for(k=0;k n;k+)if(ink=pre0)break;if(!k rchild);push(p-lchild);非递归算法,考虑取消多余的空指针入栈,空指针个数可观,为n+1个
10、init_stack();p=t;push(NULL);while(p)visit(p);if(p-rchild)push(p-rchild);if(p-lchild)p=p-lchild;else p=pop();init_stack();p=t;while(!stack_empty()|p)visit(p);if(p-rchild)push(p-rchild);if(p-lchild)p=p-lchild;else p=pop();p非递归算法 init_stack();push(root,0);while(!stack_empty()p=pop(&state);if(p=NULL)con
11、tinue;if(state=0)push(p,1);push(p-lchild,0);else if(state=1)push(p,2);push(p-rchild,0);else visit(p);p非递归算法 init_stack();push(root,0);while(!stack_empty()p=pop(&state);if(p=NULL)continue;if(state=0)push(p,2);push(p-rchild,0);push(p-lchild,0);else visit(p);p朴素的算法 init_stack();push(root,0);while(!stac
12、k_empty()p=pop(&state);if(p=NULL)continue;if(state=0)push(p,1);push(p-lchild,0);else if(state=1)visit(p);push(p-rchild,0);p效率问题效率问题 新压入堆栈的新压入堆栈的state0节点马上弹出然后再次压入作为节点马上弹出然后再次压入作为state1节点节点 init_stack();p=root;while(p)push(p);p=p-lchild;while(!stack_empty()p=pop();visit(p);p=p-rchild;while(p)push(p);
13、p=p-lchild;init_stack();p=root;while(p|!stack_empty()if(p)push(p);p=p-lchild;else p=pop();visit(p);p=p-rchild;p在二叉树中查找结点值为x的结点p求二叉树中每个结点所处的层次p求二叉树的高度p复制一棵二叉树A B C D E F G 0 01033 1 2 3 4 5 6ABCEFGD 存储方法:使用顺序结构存储方法:使用顺序结构#define MAX_TREE_SIZE 100typedef struct PTNode TElemType data;int parent;PTNode;
14、/*节点的存储结构节点的存储结构*/typedef struct/*顺序存储结构顺序存储结构*/PTNode nodesMAX_TREE_SIZE;int r,n;/*根位置和节点数根位置和节点数*/PTree;优点:简单,紧凑,易于寻找双亲节点优点:简单,紧凑,易于寻找双亲节点缺点:查询某节点的孩子效率低缺点:查询某节点的孩子效率低 方法一 struct PTNode TElemType data;struct PTNode*childNUM_CHILD;缺点:NUM_CHILD值不容易确定;空域数目太多方法二:改进方法1,记录节点的度,分配合适大小的内存方法三:使用链表勾链,链表的每个节点
15、记录一个孩子指针优缺点比较和其他存储方案 选用的实际存储结构该考虑到可能需要操作(增加,删除,检索)ABCEFGDABCDEFG1234650123456#define MAX_TREE_SIZE 100struct TNode TElemType data;int degree;int child1;struct PTree struct TNode*nodeMAX_TREE_SIZE;int n;PTree;struct TNode*create_tnode(void)struct TNode*p;int k,degree;scanf(“%d”,°ree);p=malloc(size
16、of(struct TNode)+(degree-1)*sizeof(int);P-degree=degree;for(k=0;k childk);input_node_data(p);return(p);p孩子兄弟表示法ABCDEFGABCEFGDp转换原则n按孩子兄弟表示法进行转换。按孩子兄弟表示法进行转换。p树与二叉树的转换DEp树的遍历ABCDE F GABECDFGn先序遍历先序遍历p先访问树的根结点,然后依次先根遍历根的每棵子树;ABEFCDG(二叉树先序)n后序遍历后序遍历p先依次后根遍历根的每棵子树,然后访问树的根结点 EFBCGDA(二叉树中序)p先序遍历:n访问森林中第一棵
17、树的根结点;n先序遍历第一棵树中根结点的子树森林;n先序遍历除第一棵树外剩余的树构成的森林;举例:A B C D E F G H I Jp中序遍历n中序遍历森林中第一棵树的根结点的子树森林;n访问第一棵树的根结点;n中序遍历除第一棵树外剩余的树构成的森林举例:B C D A F E H J I Gp集合的运算nfind(x)判断元素判断元素x属于哪个集合属于哪个集合nMerge(Si,Sj)将集合将集合Si和和Sj合并合并p集合的数据结构有多种实现方法n位图法位图法 O(1)n有序表方法有序表方法 O(n)n树型结构表示集合树型结构表示集合p采用的结构取决于集合大小和所进行的操作A B C D
18、 E F G 0 01033 1 2 3 4 5 6ABCEFGD 存储方法:使用顺序结构存储方法:使用顺序结构#define MAX_TREE_SIZE 100typedef struct PTNode TElemType data;int parent;PTNode;/*节点的存储结构节点的存储结构*/typedef struct/*顺序存储结构顺序存储结构*/PTNode nodesMAX_TREE_SIZE;int r,n;/*根位置和节点数根位置和节点数*/PTree;优点:简单,紧凑,易于寻找双亲节点优点:简单,紧凑,易于寻找双亲节点缺点:查询某节点的孩子效率低缺点:查询某节点的孩
19、子效率低p采用双亲表示法,存储一个森林type PTree MFSet;int find_mfset(MFSet S,int i)if(i=S.n)return-1;for(j=i;S.nodesj.parent=0;j=S.nodesj.parent);return j;Status merge_mfset(MFSet S,int i,int j)if(i=S.n|j=S.n)return ERROR;S.nodei.parent=j;return OK;p分析算法的效率n合并合并O(1),检索检索O(d),nd为深度,最糟糕情况下,为深度,最糟糕情况下,d=np改进思路n合并时,将数量少的
20、集合,合并到数量多的集合中合并时,将数量少的集合,合并到数量多的集合中n根的根的parent记录集合中节点个数,为区别于正常记录集合中节点个数,为区别于正常parent值,用负数值,用负数p改进后的分析n合并合并O(1),树深度低于,树深度低于log2nvoid mix_mfset(MFSet S,int i,int j)if(S.nodesi.parent S.nodesj.parent)/*i中节点少*/swap(i,j);/*i中节点多*/S.nodesi.parent+=s.nodesj.parent;S.j.parent=i;int fix_mfset(MFSet S,int i)f
21、or(j=i;S.nodesj.parent=0;j=S.nodesj.parent);for(k=i;k!=j;k=t)t=S.nodesk.parent;S.nodesk.parent=j;return j;改进思路:检索时压缩路径p赫夫曼树n最优树,是一类带权路径长度最短的树;n路径长度p指树中两个结点间路径上所含有的分支数目。n树的路径长度p指从树根到每一结点的路径长度之和。n带权路径长度p指结点的路径长度与该结点的权之积。p树的带权路径长度n树中所有叶子结点的带权路径长度;树中所有叶子结点的带权路径长度;p最优二叉树或赫夫曼树nWPL 最小的二叉树最小的二叉树结点到根的路径长度权值其
22、中:记作:kknkkklwlwwpl1p赫夫曼树中除叶子结点外,所有分支结点的度均为2;p叶子结点(外部结点)可看成是由分支结点(内部结点)组成的树扩充出来的(扩充二叉树)1.根据给定的n个权值w1,w2,.wn构成n棵二叉树的集合F=T1,T2,.,Tn,其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树均空;2.在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。3.在F中删除这两棵树,同时将新得到的二叉树加入F中;4.重复2和3,直到F中只含一棵树为止;5.这棵树就是赫夫曼树;51429 7823 311
23、1429 7823 1135887151429233581111358191429238715113581929 23148715292914871529113581923421135819234229148715295811358192342291487152958100存储结构和构造算法存储结构和构造算法p初始有n个叶子结点(0n-1号),构造出的哈夫曼树有2n-1个结点p用大小m=2n-1的向量存储哈夫曼树(顺序存储)struct HTNode int weight;int parent,lchild,rchild;*ht;m=2*n-1;ht=malloc(m*sizeof(struc
24、t HTNode);for(p=&ht0;p parent=-1;输入ht前n个元素的weight;for(i=n;i m;i+)在ht的前i个节点中寻找parent为-1且weight最小的两个节点s1,s2 hts1.parent=hts2.parent=i;hti.lchild=s1;hti.rchild=s2;hti.weight=hts1.weight+hts2.weight;例:例:已知字符 A,B,C,D,E,F,G,使用频度分别为:0.25,0.1,0.15,0.06,0.3,0.05,0.09,试以此构造霍夫曼树。GB0.19A0.44FD0.11C0.26E0.5611)0
25、.25 0.1 0.15 0.06 0.3 0.05 0.092)0.25 0.1 0.15 0.3 0.09 0.11 3)0.25 0.15 0.3 0.11 0.19 4)0.25 0.3 0.19 0.265)0.3 0.26 0.446)0.44 0.567)1.001、最佳判定树;2、霍夫曼编码:GBAFDCE000000111111A:01B:001C:101D:1001E:11F:1000G:000E:100A:000B:001C:010D:011F:101G:110000000111111GBAFDCE0WPL霍夫曼编码=2.56WPL等长编码=3 利用霍夫曼编码可提高效率:
26、(3-2.56)/3 15%霍夫曼树的应用霍夫曼树的应用p主数据结构:构造哈夫曼编码表,使用ht的parent域p辅助数据结构:char cdN;char*get_code(int i)/求i号符号的编码串 cdN-1=0;start=N-1;for(c=i,f=htc.parent;f!=-1;c=f,f=htf.parent)cd-start=htf.lchild=c?0:1;return&cdstart;p解码程序比较简单,从树根出发按1或0分别走向右或左孩子,直到叶子节点,解出编码的符号decode()/求i号符号的编码串 k=2*n-2;/*树根节点*/for(j=0;j rcv_b
27、uf_len;j+)k=rcvbufj=0?htk.lchild:htk.rchild;if(k n)out_code(k);/*解出第k号符号*/k=2*n 2;p解码程序:从树根出发按1或0分别走向右或左孩子,直到叶子节点,解出编码的符号n赫夫曼编码是不等长编码;赫夫曼编码是不等长编码;n赫夫曼编码是前缀编码,即任一字符的编码都不是另一赫夫曼编码是前缀编码,即任一字符的编码都不是另一字符编码的前缀;字符编码的前缀;n赫夫曼编码树中没有度为赫夫曼编码树中没有度为1的结点。若叶子结点的个数为的结点。若叶子结点的个数为n,则赫夫曼编码树的结点总数为,则赫夫曼编码树的结点总数为 2n-1;n发送过
28、程:根据由赫夫曼树得到的编码表送出字符数据;发送过程:根据由赫夫曼树得到的编码表送出字符数据;n接收过程:按左接收过程:按左0、右、右1的规定,从根结点走到一个叶结的规定,从根结点走到一个叶结点,完成一个字符的译码。反复此过程,直到接收数据点,完成一个字符的译码。反复此过程,直到接收数据结束;结束;1.试分别画出具有3个结点的树和3个结点的二叉树的所有不同形态。2.一棵含有n个结点的k叉树,何种形态达到最大深度?何种形态达到最小深度?3.假设一棵二叉树的先序序列为EBADCFHGIKJ和中序序列为ABCDEFGHIJK。请画出该树。4.假设用于通讯的电文仅由8个字母组成,字母在电文中出现的频率
29、分别为0.07,0.19,0.02,0.06,0.32,0.03,0.21,0.10。试为这8个字母设计哈夫曼编码。5.画出下列森林对应的二叉树。12 3 4 5 6 78910111213 146.画出和下列二叉树相应的森林 (1)(2)(3)(4)(5)7.画出和下列已知序列对应的森林F:森林的先序次序访问序列为:ABCDEFGHIJKL 森林的中序次序访问序列为:CBEFDGAJIKLHAA A A AB B B C B CC C D8.一棵完全 k 叉树按层次顺序从 1 开始对全部结点编号,若所求结点存在,则:编号为 n 的结点的父结点的编号是多少?结点 n 的第 i 个儿子的编号是多
30、少?结点 n 有右兄弟的条件是什么?9.设计算法将二叉树中所有节点的左右孩子互换。10.设计算法拷贝二叉树。11.设计算法判断一个二叉树是否是完全二叉树。(1)建立二叉树:两种方法 用先序递归过程建立二叉树(存储结构:二叉链表)输入数据按前序遍历所得序列输入,当某结点左子树或右子树为空时,输入*号,如输入abc*d*e*得到的二叉树为:ab ec d 由二叉树的层次序列和中序序列建立一棵二叉树。对上例:输入abecd和cbdae两个字符串(2)编写递归算法,先序,后序和后序输出二叉树(3)计算二叉树中叶子结点的数目,和每个节点的深度。(4)按凹入表方式输出该二叉树。(5)按层次遍历的方法输出该二叉树,求出每层的结点个数。AB CD EF应当设计多个测试实例,检验程序的正确性。下边是一个例子。