二叉排序树相关算法的排序.docx
《二叉排序树相关算法的排序.docx》由会员分享,可在线阅读,更多相关《二叉排序树相关算法的排序.docx(17页珍藏版)》请在冰豆网上搜索。
二叉排序树相关算法的排序
福建农林大学计算机与信息学院
计算机类
课程设计报告
课程名称:
数据结构
课程设计题目:
二叉排序树相关算法的实现
(作中序遍历求平均查找长度删除结点及判断是否为平衡二叉树)
姓名:
陈燕华
系:
计算机
专业:
计算机科学与技术(专升本)
年级:
06级
学号:
061142017
指导教师:
黄思先
职称:
副教授
2007年06月28日
目录
1封面………………………………………………………………………………1
1.1结果评定…………………………………………………………………2
1.2目录………………………………………………………………………3
2正文……………………………………………………………………………4
2.1课程设计目的……………………………………………………………4
2.2课程设计要求……………………………………………………………4
2.3课程设计内容……………………………………………………………4
2.4设计原理、框图…………………………………………………………8
2.5程序调试与测试………………………………………………………9
2.6运行结果…………………………………………………………………9
2.7程序码…………………………………………………………………10
3总结…………………………………………………………………………15
4参考文献………………………………………………………………………16
二叉排序树相关算法的实现
——作中序遍历求平均查找长度删除结点及判断是否为平衡二叉树
2.1课程设计的目的
此次课程设计的目的是以C语言为基础,通过完成一些具有一定难度的课程设计题目的编写、调试、运行工作,进一步了解并掌握数据结构与算法的设计方法,具备初步的独立分析和设计能力;初步掌握软件开发过程的问题分析、系统设计、程序编码、测试等基本方法和技能;巩固所学理论知识,使理论与实际相结合。
从而提高自我分析问题、解决问题的能力。
用系统的观点和软件开发一般规范进行软件开发,培养软件工作者所应具备的科学的工作方法和作风,同时培养学生调查研究、查阅技术文献、资料、手册以及编写技术文献的能力。
2.2课程设计要求
①、设计的课题能够体现数据结构和算法的算法分析、设计、算法实现。
②、根据自己对数据结构和算法的基本概念、原理和机制的理解,自拟题目和设计内容,选题尽可能结合实际的应用。
2.3课程设计报告内容
一、程序中所采用的数据结构及存储结构的说明
程序中的数据采用“树形结构”作为其数据结构。
具体的,采用的是“二叉排序树”。
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;(3)它的左右子树也分别为二叉排序树。
程序中分别采用了“二插链表”和“一维数组”作为其存储结构。
二插链表存储结构中二插树的结点由一个数据元素和分别指向其左、右子树的两个分支构成。
如:
程序中采用的结构是:
typedefstructTnode{
intdata;/*数据元素*/
structTnode*lchild,*rchild;/*左右指针*/
}*node,BSTnode;
一维数组顺序表存储结构是用一组地址连续的存储单元依次自上而下、自左而右存储完全二插树上的结点元素,即将完全二叉树上编号为i的结点元素存储在如上定义的一维数组中下标为i-1的分量中。
利用顺序表作为存储结构:
typedefstruct{
int*data;/*一维数组基址*/
intlenth;/*一维数组的长度*/
}BST;
一维数组存储结构中结点i的父母亲为|_i/2_|,左孩子为2i,右孩子为2i+1.二、算法的设计思想
a)二插链表作存储结构:
建立二插排序树采用边查找边插入的方式。
查找函数采用递归的方式进行查找。
如果查找成功则不应再插入原树,否则返回当前结点的上一个结点。
然后利用插入函数将该元素插入原树。
对二叉树进行中序遍历采用递归函数的方式。
在根结点不为空的情况下,先访问左子树,再访问根结点,最后访问右子树。
计算二插排序树的平均查找长度时,仍采用类似中序遍历的递归方式,用s记录总查找长度,j记录每个结点的查找长度,s置初值为0,采用累加的方式最终得到总查找长度s。
平均查找长度就等于s/i(i为树中结点的总个数)。
删除结点函数,采用边查找边删除的方式。
如果没有查找到,则不对树做任何的修改;如果查找到结点,则分四种情况分别进行讨论:
1、该结点左右子树均为空;2、该结点仅左子树为空;3、该结点仅右子树为空;4、该结点左右子树均不为空。
判断二插排序树是否为平衡二叉树的函数,也是采用递归函数的方式,分别判定以树中每个结点为根结点的子树是否为平衡二叉树。
只要有一个子树不为平衡二叉树,则该树便不是平衡二叉树。
b)一维数组作存储结构:
建立二插排序树,首先用一个一维数组记录下读入的数据,然后再用边查找边插入的方式将数据一一对应放在完全二叉树相应的位置,为空的树结点用“0”补齐。
中序遍历二叉树也采用递归函数的方式,先访问左子树2i,然后访问根结点i,最后访问右子树2i+1.先向左走到底再层层返回,直至所有的结点都被访问完毕。
计算二插排序树的平均查找长度时,采用类似中序遍历的递归方式,用s记录总查找长度,j记录每个结点的查找长度,s置初值为0,采用累加的方式最终得到总查找长度s。
平均查找长度就等于s/i(i为树中结点的总个数)。
删除二插排序树中某个结点,采用边查找边插入的方式,类似重新建立一个一维数组作为存储新树的空间。
将原数组中的数据一个一个的插入树中,若遇到需要删除的结点则不执行插入操作。
判断二插排序树是否为平衡二叉树的函数,也是采用递归函数的方式,分别判定以树中每个结点为根结点的子树是否为平衡二叉树。
只要有一个子树不为平衡二叉树,则该树便不是平衡二叉树。
三、平衡二叉树与未平衡化的二叉树查找效率比较
(1)对于未平衡化的二叉树:
当先后插入的关键字有序时,构成的二插排序树蜕变为单支树。
树的深度为n,其平均查找长度为(n+1)/2.这是最差的情况。
这种情况下比较关键字的最大次数为n次。
(2)最好的情况是:
建成的树是一棵平衡二叉树。
在这种情况下,二插排序树的平均查找长度和log2(n)成正比。
比较关键字的最大次数为:
0.5logψ(5)+logψ(n+1)-2次(其中ψ=(1+根号5)/2)。
(3)那么,平均情况下分析:
假设在含有n(n>=1)个关键字的序列中,i个关键字小于第一个关键字,n-i-1个关键字大于第一个关键字,则由此构造而得的二插排序树在n个记录的查找概率相等的情况下,其平均查找长度为:
P(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时,P(n)<=2(1+1/n)ln(n)
由此可见,在随机的情况下,二插排序树的平均查找长度和log(n)是等数量级的。
四、时间复杂度的分析
说明:
对时间复杂度的分析,均指在最坏情况下的时间复杂度。
二插链表存储结构中:
(1)查找函数最坏的情况是要找的点正好是二叉树的最深的叶子结点,此时
时间复杂度=O(n)。
(2)插入函数最坏的情况是要插入的点正是二叉树的最深的那一支的叶子
结点,此时时间复杂度=O(n)。
(3)中序遍历函数,求平均查找长度的函数,删除函数以及判断二插排序树是否为平衡二叉树的函数,其时间复杂度均与以上情况类似,等O(n)。
一维数组顺序表存储结构中:
(1)插入函数最坏的情况是要插入的点正是二叉树的最深的那一支的叶子结点,此时时间复杂度=O(n)。
(2)创建函数最坏的情况就是调用插入函数时插入函数遇到最坏的情况。
因此,创建函数的时间复杂度也等于O(n)。
(3)中序遍历函数,求平均查找长度的函数,查找函数,以及判断二插排序树是否为平衡二叉树的函数,其时间复杂度均与以上情况类似,等O(n)。
(4)删除函数不采用递归手法而是采用重新建立一颗不含要删结点的二插排序树。
其时间复杂度=O(n)。
2.4设计原理、框图
存在含x的结点,则删除该结点,并作中序遍历
无结点x
输入元素x,查找二叉排序树T
计算二叉排序树T的平均查找长度,并输出结果
NO!
N
Y
OK!
2.5程序调试与测试
补上;
2.6运行结果
2.7程序
#include
typedefstructTnode
{
intdata;/*输入的数据*/
structTnode*lchild,*rchild;/*结点的左右指针,分别指向结点的左右孩子*/
}*node,BSTnode;
searchBST(nodet,intkey,nodef,node*p)/*查找函数*/
{
if(!
t){*p=f;return(0);}/*查找不成功*/
elseif(key==t->data)
{
*p=t;return
(1);
}/*查找成功*/
elseif(keydata)
searchBST(t->lchild,key,t,p);/*在左子树中继续查找*/
else
searchBST(t->rchild,key,t,p);/*在右子树中继续查找*/
}
insertBST(node*t,intkey)/*插入函数*/
{
nodep=NULL,s=NULL;
if(!
searchBST(*t,key,NULL,&p))/*查找不成功*/
{
s=(node)malloc(sizeof(BSTnode));
s->data=key;
s->lchild=s->rchild=NULL;
if(!
p)*t=s;/*被插结点*s为新的根结点*/
elseif(keydata)p->lchild=s;/*被插结点*s为左孩子*/
elsep->rchild=s;/*被插结点*s为右孩子*/
return
(1);
}
elsereturn(0);/*树中已有关键字相同的结点,不再插入*/
}
inorderTraverse(node*t)/*中序遍历函数*/
{
if(*t)
{
if(inorderTraverse(&(*t)->lchild))/*中序遍历根的左子树*/
{
printf("%d",(*t)->data);/*输出根结点*/
if(inorderTraverse(&(*t)->rchild));/*中序遍历根的右子树*/
}
}
elsereturn
(1);
}
calculateASL(node*t,int*s,int*j,inti)/*计算平均查找长度*/
{
if(*t)
{i++;/*i记录当前结点的在当前树中的深度*/
*s=*s+i;/*s记录已遍历过的点的深度之和*/
if(calculateASL(&(*t)->lchild,s,j,i))/*计算左子树的ASL*/
{
(*j)++;/*j记录树中结点的数目*/
if(calculateASL(&(*t)->rchild,s,j,i))/*计算右子树的ASL*/
{i--;return
(1);}
}
}
elsereturn
(1);
}
nodeDelete(nodet,intkey)/*删除函数*/
{
nodep=t,q=NULL,s,f;
while(p!
=NULL)/*查找要删除的点*/
{
if(p->data==key)break;
q=p;
if(p->data>key)
p=p->lchild;
else
p=p->rchild;
}
if(p==NULL)
returnt;/*查找失败*/
if(p->lchild==NULL)/*p指向当前要删除的结点*/
{
if(q==NULL)
t=p->rchild;/*q指向要删结点的父母*/
elseif(q->lchild==p)
q->lchild=p->rchild;/*p为q的左孩子*/
else
q->rchild=p->rchild;/*p为q的右孩子*/
free(p);
}
else{/*p的左孩子不为空*/
f=p;
s=p->lchild;
while(s->rchild)/*左拐后向右走到底*/
{
f=s;
s=s->rchild;
}
if(f==p)
f->lchild=s->lchild;/*重接f的左子树*/
else
f->rchild=s->lchild;/*重接f的右子树*/
p->data=s->data;
free(s);
}
returnt;
}
intbalanceBST(nodet,int*i)/*判断是否为平衡二叉树的函数*/
{
intdep1,dep2;
if(!
t)return(0);
else
{
dep1=balanceBST(t->lchild,i);
dep2=balanceBST(t->rchild,i);
}
if((dep1-dep2)>1||(dep1-dep2)<-1)
*i=dep1-dep2;/*用i值记录是否存在不平衡现象*/
if(dep1>dep2)return(dep1+1);
elsereturn(dep2+1);
}
voidmain()
{
nodeT=NULL;
intnum;
ints=0,j=0,i=0;
intch=0;
nodep=NULL;
printf("pleaseinputalistofnumbersendwithzero:
");
do
{
scanf("%d",&num);
if(!
num)
printf("youhavefinishedyourinput!
\n");
elseinsertBST(&T,num);
}while(num);
printf("\\n\\n---themenuoftheopperation---\\n\\n\n");/*主程序菜单*/
printf("0:
exit\n");
printf("1:
inordertravelthetree\n");
printf("2:
theaveragesearchlengthforthetree\n");
printf("3:
delete");
printf("4:
judgethebalance\n");
while(ch==ch)
{
printf("choosetheopperationtocontinue:
");
scanf("%d",&ch);
switch(ch){
case0:
exit(0);/*0--退出*/
case1:
printf("Theresultoftheinordertraverseis:
\n");
inorderTraverse(&T);/*1--中序遍历*/
break;
case2:
s=0;j=0;i=0;
calculateASL(&T,&s,&j,i);/*2--计算平均查找长度*/
printf("ASL=%d/%d",s,j);
break;
case3:
printf("Pleaseinputthenumberyouwanttodelete:
");
scanf("%d",&num);/*3--删除某个结点*/
if(searchBST(T,num,NULL,&p))
{
T=Delete(T,num);
printf("Youhavedeletethenumbersuccessfully!
\n");
inorderTraverse(&T);
}
elseprintf("Nonode%dyouwanttodelete!
",num);
break;
case4:
i=0;
balanceBST(T,&i);/*判断是否为平衡二插树*/
if(i==0)
printf("OK!
Thetreeisabalancedtree!
");
else
printf("NO!
");
break;
default:
printf("Yourinputiswrong!
pleaseinputagain!
\n");
break;/*输入无效字符*/
}
printf("\n");
}
}
3总结
通过这次设计我着实感受了一次编程的乐趣,从中也学到了不少知识。
虽然都说“程序=数据结构+算法”,但我在学习运用数据结构编程之前,并没能深刻体会到这一点,直到这次课设实践。
我感受最深的一点是:
以前用C编程,只是注重如何编写函数能够完成所需要的功能,似乎没有明确的战术,只是凭单纯的意识和简单的语句来堆砌出一段程序。
感但现在编程感觉完全不同了。
在编写一个程序之前,自己能够综合考虑各种因素,首先选取自己需要的数据结构,是树还是图或是别的什么?
然后选定一种或几种存储结构来具体的决定后面的函数的主要风格。
最后在编写每一个函数之前,可以仔细斟酌比对,挑选出最适合当前状况的算法。
这样,即使在完整的程序还没有写出来之前,自己心中已经有了明确的原图了。
这样无形中就提高了自己编写的程序的质量。
另外,我还体会到深刻理解数据结构的重要性。
只有真正理解这样定义数据类型的好处,才能用好这样一种数据结构。
了解典型数据结构的性质是非常有用的,它往往是编写程序的关键。
在这次实验中,我对参数的调用也进行了很多种尝试,已经能够相对准确的选择合适的参数形式来实现函数之间的数据传输交互了。
这次实验中我也出现过一些比较严重的错误。
在用一维数组顺序表结构编写程序时我错误的运用静态链表来实现函数功能。
这是我对基本概念理解的模糊不清造成的。
我原以为只要采用一维数组作为存储结构它就一定也是顺序表结构,而实质上这根本是两个不相干的概念。
后来在同学的指点下我意识到自己的错误。
不过收获也很不少。
至少我又练习了运用静态链表来实现同样的功能,同时我也发现两者在很多函数上是互通的,只需稍作修改即可移植。
总之,我会继续我的兴趣编写程序的,相信在越来越多的尝试之后,自己会不断进。
4参考文献
[1]朱战立编著 .数据结构 (C语言描述) .北京:
高等教育出版社,2004
[2]严蔚敏等编著.数据结构及应用算法教程 .北京:
清华大学出版社2001
[3]陈本林等编著 .数据结构.南京:
南京大学出版社,2002
[4]赵致琢著 .计算科学导论 .北京:
科学出版社,2000