二叉排序树及平衡二叉树.docx
《二叉排序树及平衡二叉树.docx》由会员分享,可在线阅读,更多相关《二叉排序树及平衡二叉树.docx(21页珍藏版)》请在冰豆网上搜索。
![二叉排序树及平衡二叉树.docx](https://file1.bdocx.com/fileroot1/2023-1/10/97469728-7fbf-4d19-874d-62a7dd95b522/97469728-7fbf-4d19-874d-62a7dd95b5221.gif)
二叉排序树及平衡二叉树
编号:
学号:
课程设计
教学院
计算机学院
课程名称
数据结构及算法设计B
题目
二叉排序树及平衡二叉排序树
专业
计算机科学及技术
班级
姓名
甘全中
同组人员
指导教师
2016
年
12
月
26
日
一概述
1。
1课程设计的目的
1.理解和掌握该课程中的有关基本概念,程序设计思想和方法.
2.培养综合运用所学知识独立完成课题的能力.
3.培养勇于探索、严谨推理、实事求是、有错必改,用实践来检验理论,全方位考虑问题等科学技术人员应具有的素质。
4.掌握从资料文献、科学实验中获得知识的能力,提高学生从别人经验中找到解决问题的新途径的悟性,初步培养工程意识和创新能力.
5.本课程是数据结构课程的实践环节。
主要目的在于加强学生在课程中学习的相关算法和这些方法的具体应用,使学生进一步掌握在C或其他语言中应用这些算法的能力。
通过课程设计题目的练习,强化学生对所学知识的掌握及对问题分析和任务定义的理解.
另外,数据结构是计算机科学及技术专业的一门核心专业基础课程,在该专业的课程体系中起着承上启下的作用,学好数据结构对于提高理论认知水平和实践能力有着极为重要的作用.学习数据结构的最终目的是为了获得求解问题的能力。
对于现实世界中的问题,应该能从中抽象出一个适当的数学模型,该数学模型在计算机内部用相应的数据结构来表示,然后设计一个解此数学模型的算法,再进行编程调试,最后获得问题的解答。
1。
2课程设计的要求
用二叉链表作存储结构,编写程序实现二叉排序树上的基本操作:
以回车('\n')为输入结束标志,输入数列L,生成二叉排序树T;对二叉排序树T作中序遍历;计算二叉排序树T的平均查找长度,输出结果;输入元素x,查找二叉排序树T,若存在含x的结点,则删除该结点,并作中序遍历;否则输出信息“无结点x”;
判断二叉排序树T是否为平衡二叉排序树;再用数列L,生成平衡二叉排序树BT:
对平衡二叉树作中序遍历输出;当插入新元素之后,发现当前的二叉排序树BT不是平衡二叉排序树,则立即将它转换成新的平衡二叉排序树BT;当删除元素之后,发现当前的二叉排序树BT不是平衡二叉排序树,则立即将它转换成新的平衡二叉排序树BT;计算平衡的二叉排序树BT的平均查找长度,输出结果。
二总体方案设计
用二叉链表作存储结构实现二叉排序树:
(1)以回车('0')为输入结束标志,输入数列L,生成一棵二叉排序树T;
(2)对二叉排序树T作中序遍历,输出结果;
(3)求二叉排序树的平均查找长度;
(4)输入元素x,查找二叉排序树T,若存在含x的结点,则删除该结点,并作中序遍历(执行操作2);否则输出信息“无x”。
(5)判断二叉排序树是不是平衡二叉树;
(6)以回车(’0')为输入结束标志,输入数列L,生成一棵平衡二叉树T;
(7)对平衡二叉树T作中序遍历,输出结果;
(8)在平衡二叉树中插入新元素,并作中序输出;
(9)在平衡二叉树中删除元素,并作中序输出;
(10)求平衡二叉树的平均查找长度;
2。
1二叉排序树的建立
二叉排序树(BinarySortTree)又称二叉查找(搜索)树(BinarySearchTree)。
其定义为:
二叉排序树或者是空树,或者是满足如下性质的二叉树:
①若它的左子树非空,则左子树上所有结点的值均小于根结点的值;
②若它的右子树非空,则右子树上所有结点的值均大于根结点的值;
③左、右子树本身又各是一棵二叉排序树。
建二叉树的结点至少应当包含三个域,分别存放结点的数据data,左子女结点指针leftChild和右子女结点指针rightChild.整个二叉树的链表要有一个表头指针,它指向二叉树的根结点,其作用是当作树的访问点
从空的二叉排序树开始,经过一系列的查找插入操作以后,生成了一棵二叉排序树。
根据二叉排序树的定义,建立一棵二叉排序树的过程是按照待排序序列元素的先后次序,不断动态生成二叉树的结点,逐个插入到二叉树中。
若p为根结点指针,b为当前待插入元素,其过程可以描述为:
若为空树(p=nil),动态生成一个结点,其数据域为当前待插入元素b,左、右指针域为“空”,p指向该结点。
若非空树,比较b及根结点数据data(p)
如果b〈data(p),将b插入左子树中;
如果b≥data(p),将b插入右子树中;
左、右子树的插入方式及二叉排序树的插入方式相同。
不断调用上述的插入过程,直到所有待排序序列均排入后,就形成一棵二叉排序树.
由此可见,建立二叉排序树就是多次调用二叉排序树的插入算法(递归调用)。
2.2二叉排序树的中序遍历
中序遍历二叉树算法的框架是:
若二叉树为空,则空操作;否则
中序遍历左子树(L);
访问根结点(V);
中序遍历右子树(R)。
中序遍历二叉树也采用递归函数的方式,先访问左子树,然后访问根结点,最后访问右子树。
直至所有的结点都被访问完毕。
2。
3二叉排序树中元素的查找
在二叉排序树上进行查找,是一个从根结点开始,沿某一个分支逐层向下进行比较判等的过程.它可以是一个递归的过程.假设我们想要在二叉排序树中查找关键码为x的元素,查找过程从根结点开始。
如果根指针为NULL,则查找不成功;否则用给定值x及根结点的关键码进行比较;如果给定值等于根结点的关键码,则查找成功,返回查找成功的信息,并报告查找到的结点地址.如果给定值小于根结点的关键码,则继续递归查找根结点的左子树;否则,递归搜索根结点的右子树.
2.4二叉排序树中元素的删除
对于二叉排序树,删去树上的一个结点相当于删去有序序列中的一个记录,只要在删除某个结点之后依旧保持二叉排序树的特性即可。
假设在二叉排序树上被删除结点为*p(指向结点的指针是p),其双亲结点为*f(结点指针为f),且不失一般性,可设*p是*f的左孩子,1。
若*p结点为叶子结点,即p和l均为空,只需修改其双亲结点指针即可。
2。
若*p结点只有左子树或者只有右子树,只要令左子树或右子树直接成为其双亲结点即可.3。
若左子树和右子树都不为空,令*p的直接前驱替代*p,然后从二叉排序树中删除它的直接前驱,即可。
2。
5二叉排序树的平均查找长度
计算二叉排序树的平均查找长度时,采用类似中序遍历的递归方式,用s记录总查找长度,j记录每个结点的查找长度,s置初值为0,采用累加的方式最终得到总查找长度s.平均查找长度就等于s/i(i为树中结点的总个数)。
假设在含有n(n>=1)个关键字的序列中,i个关键字小于第一个关键字,n—i—1个关键字大于第一个关键字,则由此构造而得的二叉排序树在n个记录的查找概率相等的情况下,其平均查找长度为:
ASL(n,i)=[1+i*(P(i)+1)+(n—i—1)(P(n-i-1)+1)]/n
其中P(i)为含有i个结点的二叉排序树的平均查找长度,则P(i)+1为查找左子树中每个关键字时所用比较次数的平均值,P(n—i-1)+1为查找右子树中每个关键字时所用比较次数的平均值。
又假设表中n个关键字的排列是“随机”的,即任一个关键字在序列中将是第1个,或第2个,…,或第n个的概率相同,则可对上式从i等于0至n-1取平均值。
最终会推导出:
当n>=2时,ASL(n)〈=2(1+1/n)ln(n)
由此可见,在随机的情况下,二叉排序树的平均查找长度和log(n)是等数量级的。
2.6平衡二叉树(AVL)
若T是一棵非空二叉树,其左、右子树分别为TL和TR,令hl和hr分别为左、右子树的深度。
当且仅当
①TL、TR都是平衡二叉树;
②|hl-hr|≤1;
时,则T是平衡二叉树.
构造平衡二叉排序树的过程中,每当插入一个结点时,首先检查是否因插入而破坏了树的平衡性,如果是因插入结点而破坏了树的平衡性,则找出其中最小不平衡子树,在保持排序树特性的前提下,调整最小不平衡子树中各结点之间的连接关系,以达到新的平衡.
最小不平衡子树:
以离插入结点最近、且平衡因子绝对值大于1的结点作根结点的子树.
假设二叉排序树的最小不平衡子树的根结点为A,则调整该子树的规律可归纳为下列四种情况:
(1)LL型:
新结点X插在A的左孩子的左子树里。
调整方法见上图。
图中以B为轴心,将A结点从B的右上方转到B的右下侧,使A成为B的右孩子。
(2)RR型:
新结点X插在A的右孩子的右子树里。
调整方法见上图。
图中以B为轴心,将A结点从B的左上方转到B的左下侧,使A成为B的左孩子。
(3)LR型:
新结点X插在A的左孩子的右子树里。
调整方法见图1-5.分为两步进行:
第一步以X为轴心,将B从X的左上方转到X的左下侧,使B成为X的左孩子,X成为A的左孩子。
第二步跟LL型一样处理(应以X为轴心)。
(4)RL型:
新结点X插在A的右孩子的左子树里。
调整方法见上图。
分为两步进行:
第一步以X为轴心,将B从X的右上方转到X的右下侧,使B成为X的右孩子,X成为A的右孩子。
第二步跟RR型一样处理(应以X为轴心)。
2.7中序输出平衡二叉树
右遍历的定义可知,中序遍历二叉树的递规算法可以定义为:
若二叉树为空,则空操作;否则中序遍历左子树,访问根结点,中序遍历右子树。
2.8在平衡二叉排序树上插入一个新元素
1.若BBST为空树,则插入一个数据元素为e的新结点作为BBST的根结点,树的深度增加1;
2.若e的关键字和BBST的根结点的关键字相等,则不进行插入;
3.若e的关键字小于BBST的根结点的关键字,而且在BBST的左子树中不存在和e有相同关键字的结点,则将e插入在BBST的左子树上。
4。
若e的关键字大于BBST的根结点的关键字,而且在BBST的右子树中不存在和e相同关键字的结点,则将e插入在BBST的右子树上
2。
9在平衡二叉排序树上删除一个元素
删除结点过程及插入结点的操作类似,基本过程是:
平衡二叉树--找到要删除的结点—-删除一个结点——变成二叉树——旋转——变回平衡二叉树。
具体过程将详细设计中的代码。
2.10求平衡二叉树的平均查找长度
计算平衡二叉排序树的平均查找长度时,采用类似中序遍历的递归方式,用s记录总查找长度,j记录每个结点的查找长度,s置初值为0,采用累加的方式最终得到总查找长度s。
平均查找长度就等于s/i(i为树中结点的总个数)。
A.使用的头文件
#includeh>
#include
B.常量定义
#defineLH+1//左高
#defineEH0//等高
#defineRH—1//右高
#defineTRUE1
#defineFALSE0
#defineEQ(a,b)((a)==(b))
#defineLT(a,b)((a)〈(b))
#defineLQ(a,b)((a)<=(b))
C.全局变量定义
inttaller=0;/*taller反映T长高及否*/
intshorter=0;/*shorter反映T变矮及否*/
D.数据结构定义
/*根据平衡二叉树特点可以定义平衡二叉树的存储结构*/
/*二叉排序树的类型定义*/
typedefstructBSTNode
{
intdata;/*结点值*/
intbf;/*结点的平衡因子*/
structBSTNode*leftchild,*rightchild;/*分别指向左右孩子的指针*/
}BSTNode,*BSTree;/*同时声明一个BSTNode和一个指针类型的*BSTree*/
三详细设计
3。
1功能设计:
输入数列时,生成平衡二叉树
函数原型:
BSTreeBuildAVLTree(BSTreet)//建立平衡二叉树函数
函数功能及思想说明:
如何使构成的二叉排序树成为平衡树呢?
先看一个具体的例子.假设表中关键字序列为(10,24,30,88,53)。
空树和1个结点10的树显然都是平衡的二叉树.在插入24之后依是平衡的,只是根结点的平衡因子BF由0变为-1;在继续插入30之后,由于结点10的BF值由-1变成-2,由此出现不平衡现象。
此时好比一根扁担出现一头重一头轻的现象,若能将扁担的支撑点由10改成至24,扁担的两头就平衡了。
由此,可以对树左一个向左逆时针“旋转”的操作,令结点24为根,而结点10为它的左子树,此时,结点10和24的平衡因子都为0,而且依保持二叉排序树的特性。
在继续插入88和53之后,由于结点30的BF值由-1变成-2,排序树中出现了新的不平衡现象,需要调整。
当此时由于结点53插在结点88结点的左子树上,因此不能和上作简单调整。
对于以结点30为根的子树来说,即要保持二叉排序树的特性,又要平衡,则必须以53作为根结点,而使37为它的左子树的根,88成为它的左子树的根。
这好比对树作了两次“旋转”操作――先向右顺时针,后向左逆时针(见图3—1(f)-(h)),使二叉排序树由不平衡转化为平衡。
图3-1平衡二叉树的生成过程
(a)空树;(b)插入10;(c)插入24;(d)插入37;(e)向左逆时针右旋转平衡;(f)相继插入90和53;(g)第一次向右顺时针旋转;(h)第二次向左逆时针旋转平衡
函数源代码:
BSTreeBuildAVLTree(BSTreet)//建立平衡二叉树函数
{
intNodeValue;
if(t!
=NULL)//如果传入根结点不为空,则树已构建过,退出函数
{
printf("二叉平衡树已经创建!
\n");
returnNULL;
}
//根结点为空则开始构建
printf("请输入结点值,每个数以空格分开,以0结束:
");
scanf("%d”,&NodeValue);
while(NodeValue)//插入任何不为0的整数值
{
BSTreeNode=CreatNode(NodeValue);
//如果根为空,则将此结点设置为根结点,否则将此结点作为非根结点插入
if(t==NULL)
t=Node;
else
t=InsertAVL(t,NodeValue);
scanf("%d",&NodeValue);//获取用户输入的新值
}
printf(”输入完成!
\n”);
returnt;
}
BSTreeCreatNode(intNodeValue)//建立新结点
{
BSTreeNode;
Node=(BSTree)malloc(sizeof(BSTree));//申请结点空间
Node—〉data=NodeValue;//结点数据域存放NodeValue
Node->bf=0;//结点初始值为0
Node->leftchild=NULL;
Node->rightchild=NULL;
returnNode;
}
3。
2功能设计:
插入新元素之后,保证仍为平衡二叉树
原型函数:
BSTreeInsertAVL(BSTreeT,inte)
函数功能及思想说明:
在平衡的二叉排序BBST上插入一个新的数据元素e的递归算法可描述如下:
(1)若BBST为空树,则插入一个数据元素为e的新结点作为BBST的根结点,树的深度增加1;
(2)若e的关键字和BBST的根结点的关键字相等,则不进行插入;
(3)若e的关键字小于BBST的根结点的关键字,而且在BBST的左子树中不存在和e有相同关键字的结点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度增加(+1),分别就下列不同情况处理之:
①BBST的根结点的平衡因子为-1(右子树的深度大于左子树的深度):
则将根结点的平衡因子更改为0,BBST的深度不变;
②BBST的根结点的平衡因子为0(左、右子树的深度相等):
则将根结点的平衡因子更改为1,BBST的深度增加1;
③BBST的根结点的平衡因子为1(左子树的深度大于右子树的深度):
若BBST的左子树根结点的平衡因子为1,则需进行单向右旋转平衡处理,并且在右旋转处理之后,将根结点和其右子树根结点的平衡因子更改为0,树的深度不变;
若BBST的左子树根结点的平衡因子为—1,则需进行先向左、后向右的双向旋转平衡处理,并且在旋转处理之后,修改根结点和其左、右子树根结点的平衡因子,树的深度不变;
(4)若e的关键字大于BBST的根结点的关键字,而且在BBST的右子树中不存在和e有相同关键字的结点,则将e插入在BBST的右子树上,并且当插之后的右子树深度增加(+1)时,分别就不同的情况处理之.
4种插入中,
(1)和(3)对称,
(2)和(4)对称.旋转操作的正确性容易由“保持二叉排序树的特性:
中序遍历所得关键字序列自小到大有序”证明之。
当平衡二叉树因插入或者删除结点而失去平衡时,仅需对最小不平衡子树进行平衡旋转处理即可。
因为经过旋转处理之后的子树深度和插入或删除之前相同,因而不影响插入或删除路径上所有祖先结点的平衡.
主要部分的详细流程图(右平衡):
以指针T所指结点为根的二叉树作右平衡旋转处理
bf=RH
函数源代码:
//对以p为根的树作右旋处理,处理之p指向新的树根结点即旋转处理之前的左子树根结点
BSTreeR_Rotate(BSTreep)
{
BSTNode*lc;//声明BSTNode*临时变量
lc=p-〉leftchild;//lc指向的*p的左子树根结点
p-〉leftchild=lc-〉rightchild;//lc的右子树挂接为*p的左子树
lc—〉rightchild=p;
p=lc;//p指向新的根结点
returnp;//返回新的根结点
}
//对以p为根的树作左旋处理,处理之p指向新的树根结点即旋转处理之前的右子树根结点
BSTreeL_Rotate(BSTreep)
{
BSTNode*rc;//声明BSTNode*临时变量
rc=p—〉rightchild;//rc指向的*p的右子树根结点
p—〉rightchild=rc—>leftchild;//rc的左子树挂接为*p的右子树
rc-〉leftchild=p;
p=rc;//p指向新的根结点
returnp;//返回新的根结点
}
//对以指针T所指结点为根的二叉树作左平衡旋转处理,本算法结束时指针T指向新的根结点
BSTreeLeftBalance(BSTreeT)
{
BSTNode*lc,*rd;
lc=T-〉leftchild;//lc指向*T的左子树根结点
switch(lc-〉bf)//检查*T的左子树平衡度,并做相应的平衡处理
{
caseLH:
//新结点插入在*T的左孩子的左子树上,要做单右旋处理
T—>bf=lc->bf=EH;
T=R_Rotate(T);
break;
caseRH:
//新结点插入在*T的左孩子的右子树上,要做双旋处理
rd=lc-〉rightchild;//rd指向*T的左孩子的右子树根
switch(rd—>bf)//修改*T及其左孩子的平衡因子
{
caseLH:
T->bf=RH;
lc->bf=EH;
break;
caseEH:
T-〉bf=lc—>bf=EH;
break;
caseRH:
T—>bf=EH;
lc-〉bf=LH;
break;
}
rd—〉bf=EH;
T—>leftchild=L_Rotate(T->leftchild);//对*T的左孩子做左旋平衡处理
T=R_Rotate(T);//对*T做右旋处理
}
returnT;
}
//对以指针T所指结点为根的二叉树作右平衡旋转处理,本算法结束时指针T指向新的根结点
BSTreeRightBalance(BSTreeT)
{
BSTreerc,ld;
rc=T-〉rightchild;//rc指向*T的右子树根结点
switch(rc-〉bf)//检查*T的右子树平衡度,并做相应的平衡处理
{
caseRH:
//新结点插入在*T的右孩子的右子树上,要做单右旋处理
T—〉bf=rc—〉bf=EH;
T=L_Rotate(T);
break;
caseLH:
//新结点插入在*T的右孩子的左子树上,要做双旋处理
ld=rc—〉leftchild;//ld指向*T的右孩子的左子树根
switch(ld-〉bf)//修改*T及其右孩子的平衡因子
{
caseLH:
T-〉bf=LH;
rc-〉bf=EH;
break;
caseEH:
T-〉bf=rc->bf=EH;
break;
caseRH:
T—〉bf=EH;
rc—〉bf=RH;
break;
}
ld—>bf=EH;
T->rightchild=R_Rotate(T->rightchild);//对*T的右孩子做右旋平衡处理
T=L_Rotate(T);//对*T做左旋处理
}
returnT;
}
//若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个数据元素为e的新结点,
//并返回插入后所建成的平衡二叉排序树,否则返回NULL.
//若因插入而使二叉数失去平衡,则作平衡旋转处理,布尔变量taller反映T长高及否
BSTreeInsertAVL(BSTreeT,inte)
{
BSTreep;
//插入新结点,树长高置taller为TRUE
if(!
T)
{
T=(BSTree)malloc(sizeof(BSTNode));
T—〉data=e;
T->leftchild=T->rightchild=NULL;
T-〉bf=EH;//此时平衡因子为0,EH等高.
taller=TRUE;//树的深度增加
}
else
{
//树中存在和e有相同关键字的结点则不再插入
if(e==T—〉data)
{
taller=FALSE;
returnNULL;
}
//值小于则继续在树的左子树中搜索
if(e〈T->data)
{
p=Insert