实验题目
实验题目: 二叉排序树。任意给定一组数据,设计一个算法,建立一棵二叉排序树,对它进行查找、插入、删除操作。
实验说明:二叉排序树存储结构如下:
二叉排序树插入算法伪代码如下:
二叉排序树中删除一个结点f的左孩子结点p算法伪代码如下:
实验内容
1.问题设计分析
插入:在二叉排序树bt中插入一个关键字为k的结点,要保证插入后仍满足BST性质。其插入过程是:若bt为空,则创建一个key域为k的结点bt,将它作为根结点;否则将k和根结点的关键字比较,若k<bt->key,则将k插入bt结点的左子树中,若k>bt->key,则将k插入bt结点的右子树中,其他情况是k=bt->key,说明树中已有此关键字k,无须插入,最后返回插入后的二叉排序树的根结点bt。
创建:创建一棵二叉排序树是从一个空树开始,每插入一个关键字,就调用一次插入算法将它插入当前已生成的二叉排序树中。
查找:因为二叉排序树可看作有序的,所以在二叉排序树上进行查找和折半查找类似,也是一个逐步缩小查找范围的过程。递归查找算法SearchBST如下(在二叉排序树bt上查找关键字为k的结点,成功时返回该结点的地址,否则返回NULL)
删除:从二叉排序树中删除一个结点时,不能直接把以该结点为根的子树都删除,只能删除该结点本身,并且还要保证删除后所得的二叉树仍然满足BST性质。也就是说,在二叉排序树中删除一个结点就相当于删除有序序列中的一个结点。删除操作必须首先进行查找,假设在查找结束时p指向要删除的结点。删除过程分为以下几种情况:
(1)若p结点是叶子结点,直接删除该结点。
(2)若p结点只有左子树而无右子树,根据二叉排序树的特点,可以直接用其左孩子替代结点p。
(3)若p结点只有右子树而无左子树。根据二叉排序树的特点,可以直接用其右孩子替代结点p。
(4)若p结点同时存在左、右子树。根据二叉排序树的特点,可以从其左子树中选择关键字最大的结点q,用结点q的值替代结点p的值,并删除结点q,其原理是用中序前驱替代被删结点。
2.程序编码
#include<stdio.h>
#include<malloc.h>
#define MaxSize 100
typedef int KeyType;
typedef char InfoType;
typedef struct node
{
KeyType key;
InfoType data;
struct node *lchild,*rchild;
} BSTNode;
void DispBST(BSTNode *b);
bool InsertBST(BSTNode *&bt,KeyType k)
{
if(bt==NULL)
{
bt=(BSTNode *)malloc(sizeof(BSTNode));
bt->key=k;
bt->lchild=bt->rchild=NULL;
return true;
}
else if(k==bt->key)
return false;
else if(k<bt->key)
return InsertBST(bt->lchild,k);
else
return InsertBST(bt->rchild,k);
}
BSTNode *CreateBST(KeyType A[],int n)
{
BSTNode *bt=NULL;
int i=0;
while(i<n)
{
if(InsertBST(bt,A[i])==1)
{
printf(" 第%d步,插入%d:",i+1,A[i]);
DispBST(bt);
printf("\n");
i++;
}
}
return bt;
}
int SearchBST(BSTNode *bt,KeyType k)
{
if(bt==NULL)
return 0;
else if(k==bt->key)
{
printf("%3d",bt->key);
return 1;
}
else if(k<bt->key)
SearchBST(bt->lchild,k);
else
SearchBST(bt->rchild,k);
printf("%3d",bt->key);
}
void DispBST(BSTNode *bt)
{
if(bt!=NULL)
{
printf("%d",bt->key);
if(bt->lchild!=NULL||bt->rchild!=NULL)
{
printf("(");
DispBST(bt->lchild);
if(bt->rchild!=NULL)
printf(",");
DispBST(bt->rchild);
printf(")");
}
}
}
void Delete1(BSTNode *p,BSTNode *&r)
{
BSTNode *q;
if(r->rchild!=NULL)
{
Delete1(p,r->rchild);
}
else
{
p->key=r->key;
p->data=r->data;
q=r;
r=r->lchild;
free(q);
}
}
void Delete(BSTNode *&p)
{
BSTNode *q;
if(p->rchild==NULL)
{
q=p;
p=p->lchild;
free(p);
}
else if(p->lchild==NULL)
{
q=p;
p=p->rchild;
free(p);
}
else Delete1(p,p->lchild);
}
bool DeleteBST(BSTNode *&bt,KeyType k)
{
if(bt==NULL)
return false;
else
{
if(k<bt->key)
return DeleteBST(bt->lchild,k);
else if(k>bt->key)
return DeleteBST(bt->rchild,k);
else
{
Delete(bt);
return true;
}
}
}
int main()
{
BSTNode *bt;
int path[MaxSize];
KeyType k=6;
int a[]={4,9,0,1,8,6,3,5,2,7},n=10;
printf("创建一颗BST树:");
printf("\n");
bt=CreateBST(a,n);
printf("BST:");
DispBST(bt);
printf("\n");
printf("查找%d关键字(逆序):",k);
SearchBST(bt,k);
printf("\n");
printf("删除结点4:");
DeleteBST(bt,4);
DispBST(bt);
printf("\n");
}
3.运行结果和分析
结果如图所示:
4.实验小结
插入:由于二叉排序树中的每个结点恰好存放一个关键字,所以插入关键字k就是插入一个结点。从插入算法InsertBST看到,每个结点插入时都需要从根结点开始比较,若比根结点的key值小,当前指针移到左子树,否则当前指针移到右子树,如此这样,直到当前指针为空,再创建一个存放关键字k的结点并链接起来。因此可知,任何结点插入二叉排序树时都是作为叶子结点插入的。
创建:一个关键字集合有多个关键字序列,不同的关键字序列采用上述创建算法得到的二叉排序树可能不同。
查找:和折半查找的判定树类似,将二叉排序树中的结点作为内部结点,可以添加相应的外部结点,具有n个内部结点的二叉排序树,其外部结点的个数为n+1。显然,在二叉排序树中进行查找,若查找成功,则是走了一条从根结点到某个内部结点的路径;若查找不成功,则是走了一条从根结点到某个外部结点的路径。因此与折半查找类似,其关键字比较的次数不超过树的高度。
删除:除上面意外,还可以从其右子树中选择关键字最小的结点g,用结点q的值替代结点p的值,其原理是用中序后继替代被删结点。