1.二叉排序树的简介:
当用线性表作为表的组织形式时,可以有三种查找法。其中以二分查找效率最高。但由于二分查找要求表中结点按关键字有序,且不能用链表作存储结构,因此,当表的插入或删除操作频繁时,为维护表的有序性,势必要移动表中很多结点。这种由移动结点引起的额外时间开销,就会抵消二分查找的优点。也就是说,二分查找只适用于静态查找表。当要进行动态高效率的查找,可以采用二叉排序树(BST)进行查找,应用二叉排序树实现查找操作属于动态查找方法。
2.二叉排序树的定义:
二叉排序树又称为二叉查找树,二叉搜索树,它或者是一棵空树,或者具有下列性质的二叉树:
1)如果左子树不为空,则左子树上所有结点的值均小于根结点的值:
2)如果右子树不为空,则右子树上所有结点的值均大于根结点的值;
3)左右子树也分别为二叉排序树;
4)树中没有值相同的结点。
3.二叉排序树的特点:
1)在实际应用中,并不能保证被查找的数据集中各元素的关键字互不相同,所以可将二叉排序树定义中BST性质(1)里的”小于”改为”大于等于”,或将BST性质(2)里的”大于”改为”小于等于”,甚至可同时修改这两个性质;
2) 二叉排序树中,每个结点关键字是惟一的;
3)如果用中序遍历来遍历二叉排序树,则遍历结果是一个递增的序列。
例如:如图所示二叉树:
中序遍历二叉排序树的结果为:15 20 30 50 60 70
4.二叉排序树的存储结构:
typedef int DataType;
typedef struct BitNode
{
DataType data;
struct BitNode* lchild,*rchild,*parent;
}BiteNode,*BiTree;
一般二叉排序树都是用二叉链表示法存储,但是二叉树的插入,删除操作中会用到其父结点指针,因此在其存储结构中增加一个父结点指针parent。
5.二叉排序树的查找:
二叉排序树最主要的功能就是查找,在二叉排序树中进行查找的思路如下:
1)将给定值key与根结点值相比较,如果相等,则查找成功;
2)如果不相等,则比较key与根结点的大小;
key大于根结点的值,则在右子树中查找;
key小于根结点的值,则在左子树中查找;
3)在左右子树中查找key时,重复上述两个步骤。
二叉排序树的查找是递归的,递归思想在二叉树中的应用非常重要。
//查找,参数:f指向T的双亲,若查找成功,则p指向该数据元素结点,返回1
//否则,指针p指向查找路径上访问的最后一个结点,返回-1
BiTree searchBST(BiTree T,int key,BiTree f,BiTree* p)
{
//树为空树
if(!T)
{
*p=f;
return NULL;
}
else if(T->data==key)//查找成功
{
*p=T;
return T;
}
else if(T->data>key)//在左子树中查找
return searchBST(T->lchild,key,T,p);
else //在右子树中查找
return searchBST(T->lchild,key,T,p);
}
在二叉排序树中进行查找和折半查找有些类似,也是一个逐步缩小查找范围的过程,若查找成功,则是一条由根结点到待查结点的路径;若查找失败,则是一条由根结点到某个叶子结点的路径。由此可知,查找过程中和关键字比较的次数不超过树的深度。
二叉排序树的平均查找长度与树的形态有关。最好的情况是二叉排序树形态比较对称,此时它与折半查找相类似,此时算法的时间复杂度大约为O(logN);最坏的情况是二叉排序树是一棵单支树(只有左子树或者右子树),那么它的查找算法复杂度为O(N).
6.二叉排序树的插入:
二叉排序树在生成时,常常需要逐个插入结点,在二叉排序树中插入新结点,要保证插入后的二叉树仍符合二叉排序树的定义。
1)如果二叉排序树T为空,则为待插入的结点key申请一个新结点,并使其为根。
2)如果二叉排序树T不为空,则将key与根结点值作比较:
如果两者相等,则说明树中已有此关键字,无需再插入;
如果key小于根结点的值,则将key插入到根的左子树中;
如果key大于根结点的值,则将key插入到根的右子树中;
3)子树中的插入与上述过程一样,如此递归下去,直到将key作为一个新的叶子结点插入到二叉排序树中或者直到发现树中已有此关键字为止。
//在二叉排序树T中插入值key
int InsertBST(BitNode *T,int key)
{
BitNode*p,*s;
//查找不成功,即树中没有值为key的结点
if(!searchBST(T,key,NULL,&p)
{
s=(BitNode*)malloc(sizeof(BitNode));
s->data=key;
s->lchild=s->rchild=NULL;
//插入s作为新的根结点
if(!p)
{
T=s;
}
else if(key<p->data)
p->lchild=s;
else
p->rchild=s;
return 1;
}
else
return 0;
}
7.二叉排序树的删除:
在二叉排序树中删除一个结点,也要保证删除结点后的二叉排序树具有原来所有的性质,如果只是删除叶子结点,那么对二叉排序树来说不会有什么影响,但若是要删除有孩子的结点,那么情况就比较复杂。主要有以下三种情况:
1)若结点为叶子结点,即左右子树均为空,由于删除叶子结点不破坏整棵树的结构,则只修改其父结点的指针即可。
2)若结点只有左子树或只有右子树,则只需要让其左右子树代替父结点的,这也不破坏二叉排序树的特性。
3)若结点左右子树都不为空,则在删除时会比较麻烦,假如要删除的结点为p,一种方法是找到其中序遍历的前驱结点pre,用pre替代结点p,然后将pre删除。
另一种方法是找到结点p的中序后继结点next,则用next替换p,再将next删除。
void DeleteBST(BitNode* T,int key)
{
//先在树中查找是否存在值为key的结点
BitNode* q,*s;
//查找这个结点
BitNode* node=searchBST(T,key,NULL,&s);
if(node==NULL)
{
printf("书中没有此结点!");
return 0;
}
else
{
//如果是叶子结点
if(node->lchild==NULL&&node->rchild==NULL)
{
printf("此结点是一个叶子结点,删除!");
node->parent->lchild=node->parent->rchild=NULL;
free(node);
}
//如果右子树为空,只需要重新连接它的左子树
else if(node->rchild==NULL)
{
printf("此结点只有左子树,删除!");
//q指针指向node结点,即要删除的结点
q=node;
//node移到其左孩子结点处
node=node->lchild;
//如果q是其父结点的右孩子
if(q==q->parent->rchild)
q->parent->rchild=node;
else
q->parent->lchild=node;
free(q);
}
//如果左子树为空,只需要重新连接它的右子树
else if(node->lchild==NULL)
{
printf("此结点只有右子树,删除!");
q=node;
node=node->rchild;
if(q==q->parent->rchild)
q->parent->rchild=node;
else
q->parent->lchild=node;
free(q);
}
//如果结点左右子树均不为空
else
{
q=node;
s=node->lchild;//s指向其左子树
//寻找其中序前驱,其中序前驱在其左子树的右下角
while(s->rchild)
{
q=s;
s=s->rchild;
}
//循环结束,s就指向要删除结点的中序前驱
node->data=s->data;//将s结点的数据复制到node结点
if(q!=node)
q->rchild=s->lchild;
else
q->lchild=s->lchild;
free(s);
}
}
}