第5章 树和二叉树 北京师范大学 教育技术学院 杨开城.

Slides:



Advertisements
Similar presentations
二叉树在树结构的应用中起着非常重要的作用,因为对二叉树的许多操作算法简单,而任何树都可以与二叉树 相互转换,这样就解决了树的
Advertisements

第7章 樹與二元樹 (Trees and Binary Trees)
主讲:计算机工程学院 李兰 答疑地点:主教学楼B区213
数据结构学习考 复习课(2) 主要内容: 第三部分:树、二叉树、森林.
数据结构——树和二叉树 1/96.
第6章 树和二叉树 树型结构是一类非常重要的非线性结构。直观地,树型结构是以分支关系定义的层次结构。
助教:李伟力 电子科学与技术系 数据结构第二次习题课 助教:李伟力 电子科学与技术系
实用数据结构基础 第6章 树.
第六章 树和二叉树.
第6章 树 数据结构(C++描述).
第6章 树和二叉树 (Tree & Binary Tree)
CH6 树和二叉树 6.1 树的定义和基本术语 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树和森林
数据结构与算法 Data Structure Algorithms
数据结构 第六章 树与二叉树 深圳大学计算机系 蔡茂国.
第六章 二叉树和树 6.1 二叉树 6.2 二叉树的基本操作与存储实现 6.3 二叉树的遍历 6.4 线索二叉树 6.5 树和森林
第6章 树与二叉树.
树.
数据结构 第6章 树和二叉树 什么是树和二叉树?? 二叉树的遍历 数据结构.
树(三) 2012初赛知识点梳理.
第六章 树和二叉树 6.1 树的定义和基本术语 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树和森林 6.6 赫夫曼树及其应用.
树和二叉树(四).
第五章 树及二叉树 1.树的定义和术语 2.二叉树:定义、性质、存储 3.二叉树的遍历 4. 二叉树遍历的迭代器类 *5. 中序穿线树 6. 最优二叉树及其应用 7. 树和森林.
Chapter 5 Tree & Binary Tree
第6章 树和二叉树 6.1 树的有关概念 6.2 二叉树 6.3 二叉树的遍历 6.4 遍历的应用 6.5 线索二叉树 6.6 树和森林
第六章 树和二叉树.
赵海燕 软件研究所 14 Apr 第5章 树与二叉树 之三 赵海燕 软件研究所 14 Apr
第五章 树与二叉树 树和森林的概念 二叉树 二叉树遍历 线索化二叉树 树与森林 堆 Huffman树.
树和二叉树(三).
数 据 结 构 Ch.6 树 计 算 机 学 院 肖明军
Ch.6 树
强连通分量 无向图 1、任意两顶点连通称该图为连通图 2、否则将其中的极大连通子图称为连通分量 A D C B E 有向图
二叉树 二叉树 遍历 递归.
第六章 二叉树和树 6.1树的基本概念 6.2二叉树 6.3二叉树遍历 6.4线索二叉树 6.5树和森林 6.6树的应用(霍夫曼树及其编码)
第六章 树与二叉树 6.1 树的定义和基本术语 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树和森林
湖北大学知行学院 教师:涂晓帆 Sunday, December 09, 2018
第六章 树和二叉树.
第8章 树和二叉树 树 二叉树 二叉树设计 二叉树遍历 线索二叉树 哈夫曼树 等价问题 树与二叉树的转换 树的遍历 主要知识点.
第7章 查找 北京师范大学 教育技术学院 杨开城.
陈海明 副教授 信息学院 计算机系 电子信息类非计算机专业选修课 程序设计实践 陈海明 副教授 信息学院 计算机系
数据结构 Data Structure 主讲人:王国军,郑瑾 CSU 中南大学信息院计科系
第5章 树和二叉树 5.1树 5.2二叉树 5.3二叉树的遍历 5.4线索二叉树 5.5树、森林与二叉树的转换 5.6哈夫曼树.
数据结构 Data Structure 主讲人:王国军,郑瑾 CSU 中南大学信息院计科系
第11讲 树和二叉树(二).
第六章 树 2019/1/14.
第六章 树与森林 树和森林的概念 二叉树 (Binary Tree) 二叉树的表示
第五章 树 5.1 树的定义 树是一类重要的非线性数据结构,是以分支关系定义的层次结构 定义
数据结构概论 第6章 树和二叉树 董黎刚 浙江工商大学信电学院.
6.6 Huffman树及其应用 王 玲.
6.3 遍历二叉树和线索二叉树(知识点二) 遍历二叉树 一、问题的提出
Tree & Binary Tree.
Tree & Binary Tree.
第6章 树和二叉树 本章主题:树、二叉树 教学目的:掌握树和二叉树的类型定义、运算及存储结构 教学重点:树的各种表示、各种存储方式和运算,
无向树和根树.
第六章 树和二叉树 学习要点 理解树的定义和基本术语,重点了解二叉树的定义、性质、存储结构; 掌握二叉树遍历的递归算法及它的典型运算;
数据结构习题课 信息学院 赵明 2005年秋.
第7章 樹與二元樹(Trees and Binary Trees)
第 四 讲 线性表(二).
第六章 树和二叉树.
§6.7 子空间的直和 一、直和的定义 二、直和的判定 三、多个子空间的直和.
树和二叉树(一).
3.16 枚举算法及其程序实现 ——数组的作用.
第六章 二叉树和树 6.1二叉树 6.2二叉树遍历 6.3树和森林 6.4树的应用.
第5节 树 前两章学习的栈和队列属于线性结构。在这种结构中,数据元素的逻辑位置之间呈线性关系,每一个数据元素通常只有一个前件(除第一个元素外)和一个后件(除最后一个元素外)。在实际生活中,可以用线性结构描述数据元素之间逻辑关系的问题是很广泛的,但也有很多问题不能依靠线性结构来解决,例如家谱、行政组织机构等都是非线性的数据结构。其中树就是一种非线性的数据结构。
树和图 tree and graph 蔡亚星.
树和二叉树(四).
#define RBTREE_RED 0 #define RBTREE_BLACK 1 typedef int dataType; typedef struct treeNode{ dataType key; int color; //red:0.
本节内容 1.二叉排序树的定义 2.二叉排序树的查找操作 3.二叉排序树的插入操作 4.二叉排序树的删除操作 5.二叉排序树的总结
本节内容 1. 平衡二叉树的定义 2.平衡化旋转 3.平衡二叉排序树的插入操作 昆山爱达人信息技术有限公司
最小生成树 最优二叉树.
第五章 树和二叉树.
Presentation transcript:

第5章 树和二叉树 北京师范大学 教育技术学院 杨开城

一、树的基本概念 基本术语 树的定义 任何一个非空树是这样一个包含n个结点的有限集合: ⑴唯一存在一个结点R,它没有前驱,被称为根; ⑵当n>1时,除了根之外,其他结点可分为m(m>0)个不相交的子集T1,T2,…,Tm,其中每一个子集都是一棵树,它们的根的前驱是R,这些子集被称为R的子树。 基本术语 根 叶子 分枝结点 内部结点 双亲 孩子 祖先 子孙 兄弟 堂兄弟 度 层次 深度(高度) 有序树 无序树 森林

二、二叉树——定义和基本性质(1) 定义:二叉树(Binary Tree)是一种特殊的树形结构。它的特点是每个结点最多有两个子树,而且子树分左右,不能任意颠倒,我们通常称之为左子树和右子树。

二、二叉树——定义和基本性质(2) 基本性质: ⑴二叉树的第i层最多有2i-1(i≥1)个结点。 ⑵深度是k的二叉树,最多有2k-1个结点。 ⑶设任意一棵二叉树中,叶子结点数为n0,度是1的结点(即单枝结点)数为n1,度为2的结点(即双枝结点)数为n2,则有:n0=n2+1。 ⑷具有n个结点的完全二叉树的深度是 ⑸对于一个n个结点的完全二叉树,自上而下、自左向右给结点编号,根结点编号为1。如果已知某结点编号是i,那么 ①如果i=1,那么结点是根,如果i>1,那么它的双亲是i/2,即 ②如果2i>n,那么该结点没有左孩子,否则它的左孩子是2i; ③如果2i+1>n,那么该结点没有右孩子,否则它的右孩子是2i+1。

二、二叉树——存储结构(1) 1.二叉树的顺序存储 #define N 50 /*最大结点数*/ typedef elemtype SQBTREE[N+1]; /*数组0号单元空闲,不存放结点*/

二、二叉树——存储结构(2) 2.二叉树的链式存储 二叉链表类型定义: typedef struct btreenode { char data;/*数据域可以是任意类型*/ struct btreenode *lchild,*rchild; /*指向左右孩子的指针*/ } BTREENODE, *BTREENODEPTR,*BTREE; 三叉链表类型定义: char data; struct btreenode *lchild,*rchild; struct btreenode *parent; /*指向双亲的指针*/ } PBTREENODE, *PBTREENODEPTR,*PBTREE;

二、二叉树——建立与销毁(1) 1.二叉树的建立——以广义表字符串作为输入 【算法思想】 例子: A(B(D( , H(J , )), E( , I)), C(F , G)) 【算法思想】 从左向右扫描广义表字符串,我们会遇到这样几种字符: ①字母,字母代表结点,遇到字母时,当然要建立结点。 需要设定一个栈,将所有左右孩子没有生成完毕的结点保存起来。当前结点的双亲必然是栈顶结点。 ②左括号,左括号前面一定是一个字母。因此遇到左括号时,意味着要生成前面字母结点的左右孩子,这时要将前面字母的结点入栈。 ③逗号,逗号的前面是某个结点的左孩子,后面是某个结点的右孩子。 ④右括号,右括号意味着某个结点的左右孩子都生成完毕,这个结点一定位于栈顶。这时需要出栈操作。

二、二叉树——建立与销毁(2) typedef BTREENODEPTR elemtype; BTREE CreateBtree1(char *str) {/*字符串str是广义表字符串,以它作为输入数据,返回建立的二叉树*/ BTREE root=NULL;/*二叉树根结点指针*/ BTREENODEPTR p; int tag,i,len; int mark;/*记录扫描到的字符类型,1代表字母,2代表(,3代表逗号,4代表)*/ SQSTACK s; /*栈中存放左右孩子为生成完毕的结点*/ if(str[0]==0)return root; /*广义表是空,返回NULL*/ root=(BTREENODEPTR)malloc(sizeof(BTREENODE));/*生成根结点*/ if(root==NULL)return root; /*假设str[0]是字母*/ root->data=str[0]; root->lchild=root->rchild=NULL; len=strlen(str); /*初始化栈,容量是字符串的长度*/ InitSqstack(&s,len);

二、二叉树——建立与销毁(3) p=root; /*p指向当前生成的结点*/ mark=1; /*标明扫描到字母*/ for(i=1;str[i]!=0;i++) /*开始从左向右扫描字符串*/ switch(str[i]) { case '(': if(mark==2) return NULL; /*连续出现(,非法*/ mark=2;/*扫描到左括号*/ Push(&s,p);/*将当前结点入栈*/ tag=0;/*标明准备生成左孩子*/ break; case ')': mark=4;/*扫描到右括号*/ /*栈顶结点左右孩子生成完毕,出栈*/ if(!Pop(&s,&p))/*括号不配对*/ return NULL;

二、二叉树——建立与销毁(4) case ',': if(mark==3) return NULL; /*连续出现逗号,广义表非法*/ tag=1; /*准备生成栈顶结点的右孩子*/ break; default: if(mark==1) return NULL; /*连续出现两个字母,广义表非法*/ mark=1; /*扫描到字母*/ /*如果栈空,找不到当前结点的双亲,广义表非法*/ if(IsSqstackEmpty(s)) return NULL; /*生成当前结点,并与栈顶结点建立孩子关系*/ p=(BTREENODEPTR)malloc(sizeof(BTREENODE)); if(p==NULL) return NULL; p->data=str[i]; p->lchild=p->rchild=NULL; if(tag==0) s.elem[s.top]->lchild=p; /*当前结点是栈顶结点的左孩子*/ else s.elem[s.top]->rchild=p; /*当前结点是栈顶结点的左孩子*/ break; } return root; }

二、二叉树——建立与销毁(4) 2.二叉树的销毁 void DestroyBtree(BTREE root) {/*销毁二叉树的递归算法*/ A B C D E 2.二叉树的销毁 void DestroyBtree(BTREE root) {/*销毁二叉树的递归算法*/ if(root==NULL)return; /*销毁左子树*/ DestroyBtree(root->lchild); /*销毁右子树*/ DestroyBtree(root->rchild); root->lchild=NULL; root->rchild=NULL; free(root);/*释放结点内存*/ }

三、二叉树的遍历——算法(1) 遍历的规则: 遍历的递归算法 先序遍历:A B D H J E I C F G 中序遍历:D J H B E I A F C G 后序遍历:J H D I E B F G C A 层序遍历:A B C D E F G H I J 遍历的递归算法 ⑴先序遍历 void PreOrder(BTREE root, char *str) { /*先序遍历二叉树root,将遍历结果存放在str中,str一开始为空*/ char tmpstr[10]; if(root==NULL)return; sprintf(tmpstr,"%c ",root->data);/*将数据域数据转化为字符串tmpstr*/ strcat(str,tmpstr);/*将tmpstr续接到str后*/ PreOrder(root->lchild,str);/*遍历左子树*/ PreOrder(root->rchild,str);/*遍历右子树*/ }

三、二叉树的遍历——算法(2) 遍历的递归算法 ⑵中序遍历 void InOrder(BTREE root,char *str) {/*中序遍历二叉树root,将遍历结果存放在str中,str一开始为空*/ char tmpstr[10]; if(root==NULL)return; InOrder(root->lchild,str); /*遍历左子树*/ /*开始访问根结点*/ sprintf(tmpstr,"%c ",root->data); /*将数据域数据转化为字符串tmpstr*/ strcat(str,tmpstr); /*将tmpstr续接到str后*/ InOrder(root->rchild,str); /*遍历右子树*/ }

三、二叉树的遍历——算法(3) 遍历的递归算法 调用方法 ⑶后序遍历 void PostOrder(BTREE root,char *str) {/*后序遍历二叉树root,将遍历结果存放在str中,str一开始为空*/ char tmpstr[10]; if(root==NULL)return; PostOrder(root->lchild,str); /*遍历左子树*/ PostOrder(root->rchild,str); /*遍历右子树*/ /*开始访问根结点*/ sprintf(tmpstr,"%c ",root->data); strcat(str,tmpstr); } 调用方法 char str[80]; str[0]=0; PreOrder(root,str); printf("%s",str);

三、二叉树的遍历——算法(4) 遍历的非递归算法 ⑴先序遍历 void PreOrder1(BTREE root,char *str) {char tmpstr[20]; SQSTACK s; BTREENODEPTR p; InitSqstack(&s,MAXCOUNT);/*初始化栈,MAXCOUNT根据需要定义*/ p=root;/*p指向根结点*/ Push(&s,p);/*将当前结点入栈*/ while(!IsSqstackEmpty(s)) /*栈中有未遍历的子树*/ { Pop(&s,&p);/*弹出栈顶问题(子树p)*/ sprintf(tmpstr,"%c ",p->data); strcat(str,tmpstr); /*访问这颗子树的根结点*/ /*将p递归分解为它的左右子树,先将右子树入栈,再将左子树入栈*/ if(p->rchild!=NULL)Push(&s,p->rchild); if(p->lchild!=NULL)Push(&s,p->lchild); } DestroySqstack(&s); }

三、二叉树的遍历——算法(5) 遍历的非递归算法 ⑵中序遍历 void InOrder1(BTREE root, char *str) { char tmpstr[20]; SQSTACK s; BTREENODEPTR p; InitSqstack(&s,MAXCOUNT); p=root; Push(&s,p);/*将初始问题(根结点)入栈*/ while(1) /*无限循环,栈空退出*/ { p=s.elem[s.top]; /*取栈顶问题p*/ while(p->lchild!=NULL) /*只要当前问题p的左孩子不空,就继续向左递归分解*/ { p=p->lchild; /*p被递归分解为它的左孩子*/ Push(&s,p);/*问题p入栈,为了找到p的右孩子,生成第二个问题*/ } while(!IsSqstackEmpty(s)) /*栈中可能会连续存放递归终点的问题*/ { Pop(&s,&p);/*栈顶问题p出栈*/ sprintf(tmpstr,"%c ",p->data); strcat(str,tmpstr); /*无论p是否是递归终点,都要访问p结点自身*/

三、二叉树的遍历——算法(6) if(p->rchild!=NULL) {/*如果p的右孩子不空,将p的右孩子入栈,即将第二个问题入栈*/ Push(&s,p->rchild); break;/*转去继续递归分解*/ } if(IsSqstackEmpty(s)) {/*如果发现栈空,遍历结束*/ DestroySqstack(&s); return;

三、二叉树的遍历——算法(7) 遍历的非递归算法 ⑶后序遍历 void PostOrder1(BTREE root, char *str) {char tmpstr[20]; SQSTACK s; BTREENODEPTR p; InitSqstack(&s,MAXCOUNT); p=root; Push(&s,p);/*将问题p入栈*/ while(1) { p=s.elem[s.top];/*取栈顶问题p*/ while(p->lchild!=NULL) /*当前问题p左孩子不空,需要递归分解*/ { p=p->lchild; /*p递归分解为它的左孩子*/ Push(&s,p); /*将问题p入栈,为了找到它的右孩子,即第二个问题*/ }

三、二叉树的遍历——算法(8) while(1) /*出栈处理循环*/ { if(s.elem[s.top]->rchild!=NULL) {/*如果栈顶结点右孩子不空,栈顶问题需要继续递归分解*/ p=s.elem[s.top]->rchild; /*p指向栈顶结点的右孩子*/ Push(&s,p);/*问题p入栈*/ break;/*转去递归分解*/ } /*栈顶结点右孩子为空,才可以进行出栈处理,可能会将递归终点连续出栈*/ while(!IsSqstackEmpty(s)) { if(s.elem[s.top]->rchild==NULL||s.elem[s.top]->rchild==p) {/*栈顶问题是递归终点的条件:右孩子是空(一般首先遇到) 或者它的右孩子是原栈顶结点p(这个条件只有在出栈处理时才是递归终点的条件)*/ Pop(&s,&p); /*栈顶问题p出栈*/ sprintf(tmpstr,"%c ",p->data);/*访问结点p*/ strcat(str,tmpstr); } else break;/*否则跳出弹出递归终点的循环*/ }

三、二叉树的遍历——算法(9) if(IsSqstackEmpty(s)) {/*栈空,则遍历结束*/ DestroySqstack(&s); return; } }//出栈处理while(1)

三、二叉树的遍历——算法(10) 层序遍历 void LayerOrder(BTREE root, char *str) {/*层序遍历二叉树root,遍历结果放在str中,str一开始为空*/ char tmpstr[20]; SQQUEUE qu; BTREENODEPTR p; InitSqqueue(&qu,MAXCOUNT); EnSqqueue(&qu,root); /*将根结点入队*/ while(!IsSqqueueEmpty(qu)) /*只要队列不空就循环,队列空则遍历完毕*/ { DeSqqueue(&qu,&p);/*出队一个结点p*/ sprintf(tmpstr,"%c ",p->data); strcat(str,tmpstr); /*访问结点p*/ if(p->lchild!=NULL)/*将左孩子入队*/ EnSqqueue(&qu,p->lchild); if(p->rchild!=NULL) /*将左孩子入队*/ EnSqqueue(&qu,p->rchild); }

三、二叉树的遍历——算法的应用(1) 1.求二叉树的深度 int GetHeight(BTREE root) int lh,rh; if(root==NULL)return 0; /*空树,深度是0*/ lh=GetHeight(root->lchild); /*递归调用获得左子树的深度*/ rh=GetHeight(root->rchild);/*递归调用获得右子树的深度*/ /*取左右子树深度值大者,作为子树的深度,这个树的深度是子树深度加1*/ if(lh>=rh)return lh+1; else return rh+1; }

三、二叉树的遍历——算法的应用(2) 2.计算叶子结点的总数 int CountLeaf(BTREE root) int lnum,rnum; if(root==NULL)return 0; /*空树,叶子结点个数是0*/ lnum=CountLeaf(root->lchild); /*递归调用求左子树叶子结点个数*/ rnum=CountLeaf(root->rchild);/*递归调用求右子树叶子结点个数*/ /*如果左右子树叶子的和是0,说明当前结点是叶子,返回1;否则返回左右子树叶子结点的和*/ if((lnum+rnum)==0)return 1; else return lnum+rnum; }

三、二叉树的遍历——算法的应用(3) 3.利用层序遍历序列建立二叉树 输入数据的例子:“ABC@DE@”和“ABCDE@” BTREE CreateBtree2(char *str) { /*字符串str中是二叉树的层序输入序列字符串,返回所建立的二叉树*/ BTREE root=NULL; BTREENODEPTR p1,p2; SQQUEUE q; int k; if(str[0]==0)return root; /*生成根结点,数据域是str[0]*/ root=(BTREE)malloc(sizeof(BTREENODE)); if(root==NULL)return NULL; root->data=str[0]; root->lchild=root->rchild=NULL; /*左右孩子默认为空*/ InitSqqueue(&q,100);/*初始化队列*/ EnSqqueue(&q,root); /*将根结点入队*/

三、二叉树的遍历——算法的应用(4) k=1; /*k是字符串str的下标,从1开始向后扫描*/ while(!IsSqqueueEmpty(q)) {/*队列不空,意味着还有结点的左右孩子没有建立*/ DeSqqueue(&q,&p1); /*出队一个结点p1*/ if(str[k]!=0) /*如果字符串没有到末尾,可以为p1生成左孩子结点*/ { if(str[k]!='@') /*左孩子结点不是空,生成结点*/ { p2=(BTREENODEPTR)malloc(sizeof(BTREENODE)); if(p2==NULL) /*生成结点失败,销毁已经建立的二叉树,返回NULL*/ { DestroyBtree(root); return NULL; } p2->data=str[k]; p2->lchild=p2->rchild=NULL; p1->lchild=p2;/*p2成为p1的左孩子*/ EnSqqueue(&q,p2);/*将p2入队*/ } /*如果str[k]是@,什么都不做*/ k++;/*下标先后移动*/ }

三、二叉树的遍历——算法的应用(5) if(str[k]!=0) /*未到字符串末尾,可以为结点p1生成右孩子*/ { p2=(BTREENODEPTR)malloc(sizeof(BTREENODE)); if(p2==NULL) { DestroyBtree(root); return NULL; } p2->data=str[k]; p2->lchild=p2->rchild=NULL; p1->rchild=p2; EnSqqueue(&q,p2); } k++; }//while DestroySqqueue(&q); return root;

四、线索化二叉树(1) 【问题】已知一棵二叉树,如何在中序遍历序列中确定结点B和结点I的前驱后继结点呢? 【结论】 如果结点具有左右子树,我们很容易确定它的前驱和后继。 如果结点的左右孩子是NULL,确定起来就非常繁琐。 由于任何一个二叉树的结点都有两个指针域,当指针域是NULL时,它是空闲的。如果这种情况下,将指针域分别指向前驱和后继,对于查找结点的前驱后继是非常方便的。 用于这个目的的lchild和rchild指针被称为线索。将lchild和rchild分别指向前驱和后继的操作被称为线索化。

四、线索化二叉树(2) 线索化的二叉树的数据类型 算法思想 typedef struct thrbtreenode { char data; int ltag,rtag;/*ltag是0时,lchild指向左孩子,ltag是1时,lchild指向前驱;rtag是0时,rchild指向右孩子,rtag是1时,rchild指向后继*/ struct thrbtreenode *lchild,*rchild; } THRBTREENODE, *THRBTREENODEPTR,*THRBTREE; 算法思想 在中序遍历过程中建立线索; 设置一个全局变量保存在遍历过程中当前结点的前驱; 为了方便使用,我们为线索化了的二叉树设定另一个头结点head,head的左孩子指向真正二叉树的根结点,右孩子最终指向中序序列的最后一个结点(为了方便逆序遍历二叉树时很容易找到最后一个结点)。

四、线索化二叉树(3) 线索化算法 THRBTREENODEPTR pre;/*全局变量pre*/ THRBTREE InOrderThread(THRBTREE root) {/*为root建立线索,返回线索化了的二叉树的头结点指针*/ THRBTREE head; /*线索化二叉树的头结点*/ head=(THRBTREENODEPTR)malloc(sizeof(THRBTREENODE)); if(head==NULL)return NULL; pre=NULL;/*pre初始值是NULL*/ head->lchild=root; /*头结点左孩子指向二叉树的根*/ head->rchild=NULL; /*头结点右孩子初始化为NULL*/ InOrderThr(root);/*为二叉树建立线索*/ head->rchild=pre;/*这时,pre指向中序遍历最后一个结点*/ return head; /*返回头结点指针*/ }

四、线索化二叉树(4) void InOrderThr(THRBTREE p) /*为二叉树建立线索*/ { if(p==NULL)return; /*递归终点*/ InOrderThr(p->lchild); /*为左子树建立线索*/ /*这时pre是p的前驱*/ if(pre!=NULL&&pre->rchild==NULL) { /*如果pre不空,并且pre的右孩子是NULL,将pre的右孩子指向p*/ pre->rtag=1; /*表明pre->rchild是线索指针*/ pre->rchild=p; } if(p->lchild==NULL) { /*如果p的左孩子是NULL,将p的左孩子指向pre*/ p->ltag=1; /*表明p->lchild是线索指针*/ p->lchild=pre; pre=p; /*在遍历其他结点之前,pre指向p*/ InOrderThr(p->rchild); /*为右子树建立线索*/

四、线索化二叉树(5) 利用中序线索遍历二叉树 void TraverseInOrderThr(THRBTREE root,char *str) {/*遍历线索化了的二叉树,将遍历结果存放在str中*/ char tmpstr[20]; THRBTREENODEPTR p; p=root; /*p指向根结点*/ while(1) /*遍历循环*/ { while(p->ltag==0)/*寻找p的最左端结点*/ p=p->lchild; sprintf(tmpstr,"%c ",p->data); strcat(str,tmpstr); /*访问结点p*/ while(p->rtag==1&&p->rchild!=NULL) /*沿着线索指针遍历后继结点*/ { p=p->rchild; sprintf(tmpstr,"%c ",p->data); strcat(str,tmpstr); } if(p->rchild==NULL)return;/*遇到最后一个结点,返回*/ else p=p->rchild;/*p有右孩子,跳到右分枝*/

四、线索化二叉树(6) 先序线索化算法 void PreOrderThr(THRBTREE p) /*先序遍历的线索化操作*/ { if(p==NULL)return; /*建立pre与p之间的前驱后继关系*/ if(pre!=NULL&&pre->rchild==NULL) { pre->rtag=1; pre->rchild=p; } if(p->lchild==NULL) { p->ltag=1; p->lchild=pre; pre=p;/*pre指向p*/ if(p->ltag==0) PreOrderThr(p->lchild);/*建立左子树的线索*/ if(p->rtag==0) PreOrderThr(p->rchild);/*建立右子树的线索*/

四、线索化二叉树(7) 后序线索化算法 void PostOrderThr(THRBTREE p) /*后序遍历的线索化*/ { if(p==NULL)return; PostOrderThr(p->lchild);/*建立左子树的线索*/ PostOrderThr(p->rchild);/*建立右子树的线索*/ /*建立pre和p之间的前驱后继关系*/ if(pre!=NULL&&pre->rchild==NULL) { pre->rtag=1; pre->rchild=p; } if(p->lchild==NULL) { p->ltag=1; p->lchild=pre; pre=p;/*将p指向pre*/

五、哈夫曼树(1) 哈夫曼树,又称最优树,是一种带权路径长度最短的树。 路径长度:树中某结点到其子树另一结点之间的分枝个数。 树的路径长度:从根到每一个结点的路径长度之和。 树的带权路径长度:所有叶子结点带权路径长度之和。 假设一个二叉树有n个叶子结点,每个叶子结点的路径长度是lk ,权值是Wk ,那么整个二叉树的带权路径长度记为: 哈夫曼树可以分为二叉哈夫曼树和多叉哈夫曼树。

五、哈夫曼树(2) 【问题】在某两地之间的一次网络通讯业务中,发送的字符串是“ACCEEBBBEECCDDDDCDDEEEE”,并且已知这类通讯业务只发送ABCDE这5种字符。问如何对ABCDE这5种字符进行编码,才使得通讯时发送的数据总量最小(成本最低)? 【解决方案】 方案1:直接发送字符的ASCII码 发送的数据量: (1+3+5+6+8)×8=184(比特)。 方案2:A:000 B:001 C:010 D:011 E:100 发送的数据量:(1+3+5+6+8)×3=69(比特) 方案3:A:0 B:1 C:00 D:01 E:001 发送的数据量:1+3+5*2+6*2+8*3=50(比特) 方案4 : A:000 B:001 C:01 D:10 E:11 发送的数据量:(1+3)*3+(5+6+8)*2=50(比特)

五、哈夫曼树(3) 哈夫曼树的构造 将n个带权结点自成n个子树,每个子树只有一个根结点,这样我们就得到一个n棵二叉树的集合F={T1,T2,…,Tn}。 执行循环:选取F中权值最小的两棵树Ti和Tj,生成一棵以这两棵树为左右子树的新二叉树Tk,Tk根结点的权值是Ti和Tj根结点权值之和。将Ti,和Tj从F集合中删除,将Tk加入到F集合中。直到F集合中只有一棵二叉树为止。

五、哈夫曼树(4) 初始化存放哈夫曼树的数组 typedef struct { char ch; /*结点字符*/ int w; /*结点权值*/ int lchild,rchild;/*左右孩子,是数组下标*/ }HUFFMANNODE,*HUFFMANTREE; 初始化存放哈夫曼树的数组 HUFFMANTREE InitHuffman(void) {/*接受字符串输入,将字符出现的个数作为权值,返回只包含叶子结点的HUFFMANNODE数组,这个数组的首单元存放叶子结点的个数*/ char str[80]; int num; int i,j,k; HUFFMANTREE ht; HUFFMANNODE tmpht; printf("please input the string for huffman coding:\n"); gets(str);/*输入字符串*/ if(str[0]==0) return NULL; num=1;/*出现的字符个数,即叶子结点个数*/

五、哈夫曼树(5) for(i=1;str[i]!=0;i++) /*扫描字符串,计算有多少种字符*/ { for(j=i-1;j>=0;j--)/*检查str[0..i-1]中有无str[i]*/ if(str[i]==str[j]) break; if(j==-1) num++; /*str[i]第一次出现,num增1*/ } /*n个叶子结点的哈夫曼树,其他结点个数是n-1。还需要1个单元存放叶子结点个数*/ num=2*num; ht=(HUFFMANTREE)malloc(num*sizeof(HUFFMANNODE)); if(ht==NULL)return NULL; for(i=0;i<num;i++)/*初始化所有的数组单元,每个单元自成一棵树*/ { ht[i].w=0;/*权值归零*/ ht[i].lchild=ht[i].rchild=-1;/*左右孩子为空,用-1表示NULL*/

五、哈夫曼树(6) num=0;/*叶子结点总量*/ for(i=0;str[i]!=0;i++) /*重新扫描字符串,将计数信息作为结点的权值*/ { for(j=0;j<num;j++)/*寻找与字符str[i]相同的叶子结点*/ if(ht[j+1].ch==str[i]) break; if(j==num) /*未找到,即发现新字符*/ { ht[num+1].ch=str[i]; /*新叶子结点,记录字符信息*/ ht[num+1].w++;/*权值增1,最初是0*/ num++; /*叶子结点个数增1*/ } else ht[j+1].w++; /*找到了,相应的叶子结点权值增1*/

五、哈夫曼树(7) for(i=0;i<num-1;i++) /*对叶子结点按照权值升序排序*/ { k=i; for(j=i+1;j<num;j++) if(ht[j+1].w<ht[k+1].w) k=j; if(k!=i) { tmpht=ht[i+1]; ht[i+1]=ht[k+1]; ht[k+1]=tmpht; } ht[0].w=num;/*用ht的首单元存放叶子结点个数*/ return ht; /*返回数组*/

五、哈夫曼树(8) 生成哈夫曼树 int CreateHuffman(HUFFMANTREE ht) int i,num,k,j,root; HUFFMANNODE hfnode; num=ht[0].w; for(i=1;i<=num-1;i++) /*需要生成num-1个结点,循环num-1次*/ { k=2*i-1;/*每次取得最前面的两个未用结点,必定是两个权值最小的结点, k之前结点相当于从集合F中删除掉了*/ hfnode.w=ht[k].w+ht[k+1].w; /*生成新结点hfnode*/ /*权值大的做右孩子*/ hfnode.rchild=k+1; hfnode.lchild=k;

五、哈夫曼树(9) for(j=num+i-1;j>=k+2;j--) /*将新结点hfnode有序插入到数组中*/ if(ht[j].w>hfnode.w) ht[j+1]=ht[j]; else break; ht[j+1]=hfnode; root=j+1;/* root一直跟随新生成的结点,最后一个新生成的结点是根*/ } return root;

五、哈夫曼树(10) 哈夫曼编码的递归算法 typedef struct { char ch; /*叶子结点的字符*/ char codestr[20]; /*字符的编码01字符串*/ }HUFFMANCODE; void GetHuffmanCode(HUFFMANTREE ht,int root,char *codestr) {/*哈夫曼树树存放在ht中, root是根,codestr用来暂存叶子结点编码,一开始为空*/ int len; if(ht[root].lchild==-1) /*是叶子结点,记录叶子结点的哈夫曼编码*/ { code[codenum].ch=ht[root].ch; /*codenum是全局变量, 是已获得的编码个数,初始值是0*/ strcpy(code[codenum].codestr,codestr); codenum++;/*编码个数增1*/ }

五、哈夫曼树(11) else {/*不是递归终点,继续递归调用*/ len=strlen(codestr); codestr[len]='0'; /*左分支编码是0*/ /*向左孩子递归之前,调整路径上的编码序列,末尾增加0*/ codestr[len+1]=0; GetHuffmanCode(ht,ht[root].lchild,codestr);/*向左递归*/ /*右分支编码是1,向右孩子递归之前,将原编码末尾0改为1*/ codestr[len-1]='1'; GetHuffmanCode(ht,ht[root].rchild,codestr);/*向右递归*/ /*左右孩子递归返回后,向上返回之前,删掉编码末尾的一位*/ codestr[len-1]=0; }

六、树和森林——树的存储(1) 1.孩子表示法 #define MAXSIZE 50 typedef struct childnode { int child; /*孩子结点下标*/ struct childnode *next; /*下一个孩子*/ }CHILDNODE,*CHILDNODEPTR; typedef struct char data; /*假定树结点的数据域是char型*/ CHILDNODE childlink; /*孩子链表的头结点*/ }CTREENODE; CTREENODE node[MAXSIZE]; /*存放树结点的数组*/ int num,root; /*结点总数和根结点下标*/ }CTREE;

六、树和森林——树的存储(2) 2.孩子-双亲表示法 typedef struct { char data; /*假定树结点的数据域是char型*/ int parent; /*结点的双亲,-1表示NULL*/ CHILDNODE childlink; /*孩子链表的头结点*/ }CPTREENODE;

六、树和森林——树的存储(3) 3.孩子-兄弟表示法 typedef struct btreenode { char data;/*假设数据域是char型*/ struct btreenode * firstchild;/*指向第一个孩子结点*/ struct btreenode * nextsibling; /*指向下一个兄弟结点*/ } CSTREENODE, *CSTREENODEPTR,*CSTREE;

六、树和森林——森林与二叉树的转换

六、树和森林——树和森林的遍历 1.树的遍历 2.森林的遍历 ⑴层序遍历,即从根开始,从左向右、自上而下按层次遍历一个树。这种方式与树的二叉树表示无关。 ⑵先序遍历,即先访问根结点,然后再先序遍历它的各个子树。这个顺序与先序遍历树的二叉树表示是一致的。 ⑶后序遍历,即先后序根结点的各个子树,最后访问根结点。这个顺序与中序遍历树的二叉树表示是一致的。 2.森林的遍历 ⑴层序遍历,即将森林的各棵树的根看成是第一层结点,从这一层开始,自左向右、自上而下遍历各层结点。 ⑵先序遍历,即先序遍历第一棵树,然后先序遍历其他树。这个顺序与先序遍历森林的二叉树表示是一致的。 ⑶中序遍历,即中序遍历第一棵树根结点的子树森林,然后访问第一棵树的根结点,最后中序遍历其他树组成的森林。这个顺序与中序遍历森林的二叉树表示是一致的。

导航图(1)

导航图(2)

导航图(3)

导航图(4)

导航图(5)

导航图(6)