1、定义:
二叉排序树或者是一颗空树,或者是具有下列性质的二叉树:
(1)若它的左子树不空,则左子树上的所有结点的值均小于它的根结点的值。
(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
(3)它的左、右子树叶分别是二叉排序树。
二叉排序树是递归定义的。由定义可得:中叙遍历二叉排序树是可以得到一个结点值递增的序列。
二叉排序树的二叉链表存储表示:
typedef struct{
KeyType key; //关键字项
InfoType otherinfo; //其他数据域
}ElemType; //每个结点的数据域的类型
typedef struct BSTNode{
ElemType data; //每个结点的数据域包括关键字项和其他数据项
struct BSTNode *lchild,*rchild; //左右孩子指针
}BSTNode,*BSTree;
//===================================================
//可以把上面的结点数据域放进去,合并在一起
typedef struct BSTNode{
KeyType key; //关键字项
InfoType otherinfo; //其他数据域
struct BSTNode *lchild,*rchild; //左右孩子指针
}BSTNode,*BSTree;
一定不能缺少关键字项,二叉排序树是根据关键字项来实现存储的。
2、二叉排序树的查找
查找类似于折半查找
算法步骤:
(1)若二叉排序树为空,则查找失败,返回空指针
(2)若二叉排序树非空,则将给定值key与根结点的关键字T->data.key进行比较:
- 若key等于T->data.key,则查找成功,返回根结点地址
- 若key小于T->data.key,则递归查找左子树
- 若key大于T->data.key,则递归查找右子树
BSTree SreachBST(BSTree T,KeyType key){
if(!T||key==T->key.data). //若key等于T->data.key,则查找成功,返回根结点地址
return T;
else if(Key<T->data.key). //若key小于T->data.key,则递归查找左子树
return SreachBST(T->lchild,key);
else if(Key>T->data.key). //若key大于T->data.key,则递归查找右子树
return(T->rchild,key);
}
3、二叉排序树的插入。
二叉排序树的插入操作是以查找为基础的。要将一个关键字值为Key的结点*S插入到二叉排序树中,则需要从根结点向下查找,当树中不存在关键字值等于Key的结点时才进行插入。新插入的结点一定是一个新添加的叶子结点,并且查找不成功时查找路径上访问的最后一个结点的左孩子或右孩子结点。
算法步骤:
(1)若二叉排序树为空,则待插入结点*S作为根结点插入到空树中。
(2)若二叉排序树非空,则将Key与根结点的关键字T->data.key进行比较
- 若Key小于T->data.key,则将*S插入到左子树
- 若Key大于T->data.key, 则将*S插入到右子树
注意:这里没有给出重复值的插入方法,即若要插入的结点的关键字与二叉树中的一个结点相同的插入方法。
这里只需要做一个小小小个改变就可以,如果向把相同的放在左侧则只需要将“小于”改成“小于等于”,这样他就会成为该结点左子树上最大的一个。如果想放在右侧则只需要将“大于”改成“大于等于”,这样它就会成为该结点右侧最小的结点。
无论怎么改,中叙遍历之后仍然是单调递增的。
void InsertBST(BSTree &T,ElemType e){
if(!T){ //找到当前位置递归结束
S=new BSTNode;
S->data=e;
S->lchild=S->rchild=NULL;
T=S; //把新结点连到已找到的插入位置
}
else if(e.key<T->data.key){
//如果想将相等的结点放在左侧,则:将"<"改为“<=”
InsertBST(T->lchild,e);
}
else if(e.key>T->data.key){
//如果想将相等的结点放在右侧,则:将">"改为“>=”
InsertBST(T->rchild,e);
}
}
4、二叉排序树的创建
二叉排序树的创建是从空的二叉树开始的,没输入一个结点,经过查找操作,将新的结点插入到当前二叉树的合适位置。
算法步骤:
(1)将二叉排序树T初始化为空树。
(2)读入一个关键字为Key的结点。
(3)如果读入的关键字Key不是输入结束的标志,则循环执行一下操作:
- 将此结点插入二叉排序树T中
- 读入一个关键字为Key的结点。
void CreateBST(BSTree &T){
T=NULL;
cin>>e;
while(e.key!=ENDFLAG){
InsertBST(T,e);
cin>>e;
}
}
5、二叉搜索树的删除
被删除的二叉搜索树的结点可能是任何结点,删除结点后,要根据其位置不同修改其双亲结点及相关结点的指针,以保持二叉树的特性。
首先查找要删除的结点,如果没有找到则直接结束(说明,二叉树中没有该结点)。否则,假设被删除结点为*p,双亲结点为*f, PL和PR分别表示左子树和右子树。
删除p结点,需要考虑的情况有三种(假设p结点是父结点f的左孩子):
(1)p结点的左右子树都为空,则可以直接
f->lchild=NULL;
(2)若p结点只有左子树或者只有右子树
f->lchild=p->lchild;(只有左子树的情况)
f->lchild=p->rchild;(只有右子树的情况)
(3)若p结点的左子树和右子树都不为空,则有两种方法
首先,找到 p结点左子树的最大结点(即中序遍历中p结点的前一个结点)记为s。
方法1:令p的左子树为f的左子树,p的右子树为s的右子树
方法2:令结点s替代结点p(p->data=s->data,并不是指针的替代,而是结点里内容的替代),当直接前去s替到p时,由于s 只有左子树sL,则在删去s之后,只要令sL为s的双亲q的右子树即可(若s的双亲就是p(就是p则左子结点只有左孩子没有右孩子),则是让sL为p的左孩子)。
(此外,这里的方法1也可以令p的右子树为f的左子树,p的左子树为s(右子树最小结点,中序遍历p的后一个结点)的左子树。 方法2也可以为p的右子树的最小结点替代p.)
比较情况(3)的两种方法,第一种方法可能增加树的深度,第二种方法不会增加树的深度,所以常采用第二种方法。
以下是代码:
1、若给的查询要删除结点的条件是建二叉排序树是的关键字,则如下。
bool delete_BST(BSTree &T,int key){
BSTree p=T; //p记录要删除的结点
BSTree f=NULL; //f记录其父结点
//-------while循环找到要删除的结点p和其父结点f------------
while(!p){
if(p->price==key) break; //找到关键字等于key的结点p,结束循环
f=p; //f为p的双亲结点
if(key<p->price) //在p的左子树中继续查找
p=p->lchild;
else //在p的右子树中继续查找
p=p->rchild;
}
if(!p) return false; //如果p为NULL,说名没有该结点,返回false,表示删除不成功
//----下面是考虑三种情况实现p所指子树内部的处理:p左右子树均不空,无右子树,无左子树------
BSTree q=p; //q记录s的父节点或q记录要删除的节点p
if((p->lchild)&&(p->rchild)){ //左右子树均不空
BSTree s=p->lchild;
while(s->rchild){ //在p的左子树中继续查找其前驱结点
q=s; //q记录s的父结点
s=s->rchild; //向右到尽头
}
//s替代p
strcpy(p->ISBN, s->ISBN);
p->price=s->price;
if(q==p){ //如果s的父节点q就是p,则将s的左子树挂到p的左子树上
q->lchild=s->lchild;
}else{ //否则将s的左子树挂到s的父节点的右子树上
q->rchild=s->lchild;
}
delete s; //删除s
return true; //返回true;
}
//如果要删除结点只有一个孩子
else if(!p->lchild)
p=p->rchild;
else if(!p->rchild)
p=p->lchild;
//一下是在要删除结点p只有一个孩子情况下,判断p是父节点的左孩子还是右孩子,并挂到对应的位置
if(!f) //被删除结点就是根结点
T=p;
else if(f->lchild==q) //q(就是要删除结点,p已经是p->child了)是父节点的左孩子,则p挂到
f->lchild=p; //父节点的做孩子位置上
else if(f->rchild==q) //q是父节点的右孩子,则p挂到父结点侧右孩子位置上
f->rchild=p;
delete q; //删除q
return true;
}
2、如果给的查要删除结点的p的条件不是建二叉树时的关键字,找要删除结点p和其父结点的方法不同,即while循环不同
bool delete_BST(BSTree &T,char ISBN[]){
BSTree p=T; //p记录要删除的结点
BSTree f=NULL; //f记录其父结点
//-------下面是非递归遍历找到要删除的结点p和其父结点f------------
//先判单要删除结点是否为根结点,若为根结点,则不用遍历,直接p=T,f=NULL
//若不为根结点,则中序遍历,定义flag=false,若找到这flag=true,遍历结束若flag==false,则说明
//没找到要删除的结点,返回false结束函数。
stack<BSTree> s;
BSTree e=T;
if(strcmp(e->ISBN, ISBN)==0){
p=e;
f=NULL;
}
else{
bool flag=false;
while(e!=NULL||!s.empty()){
while(e!=NULL){
s.push(e);
e=e->lchild;
}
if(!s.empty()){
e=s.top();
s.pop();
if(e->lchild!=NULL){
if(strcmp(e->lchild->ISBN, ISBN)==0){
flag=true;
f=e;
p=e->lchild;
break;
}
}
if(e->rchild!=NULL){
if(strcmp(e->rchild->ISBN, ISBN)==0){
flag=true;
f=e;
p=e->rchild;
break;
}
}
e=e->rchild;
}
}
if(flag==false){
return false; //找不到该结点
}
}
//----下面是考虑三种情况实现p所指子树内部的处理:p左右子树均不空,无右子树,无左子树------
BSTree q=p; //q记录s的父节点或q记录要删除的节点p
if((p->lchild)&&(p->rchild)){ //左右子树均不空
BSTree s=p->lchild;
while(s->rchild){ //在p的左子树中继续查找其前驱结点
q=s; //q记录s的父结点
s=s->rchild; //向右到尽头
}
//s替代p
strcpy(p->ISBN, s->ISBN);
p->price=s->price;
if(q==p){ //如果s的父节点q就是p,则将s的左子树挂到p的左子树上
q->lchild=s->lchild;
}else{ //否则将s的左子树挂到s的父节点的右子树上
q->rchild=s->lchild;
}
delete s; //删除s
return true; //返回true;
}
//如果要删除结点只有一个孩子
else if(!p->lchild)
p=p->rchild;
else if(!p->rchild)
p=p->lchild;
//一下是在要删除结点p只有一个孩子情况下,判断p是父节点的左孩子还是右孩子,并挂到对应的位置
if(!f) //被删除结点就是根结点
T=p;
else if(f->lchild==q) //q(就是要删除结点,p已经是p->child了)是父节点的左孩子,则p挂到
f->lchild=p; //父节点的做孩子位置上
else if(f->rchild==q) //q是父节点的右孩子,则p挂到父结点侧右孩子位置上
f->rchild=p;
delete q; //删除q
return true;
}
上面两种代码是因为给定的条件不同,综合来说代码思路如下:
1、找出要删除结点p和其父结点f:
这里根据给定条件不同而不同,给定条件为建二叉排序树时关键字项时,可以用while循环的折半查找来确定p和f
如果给定的条件不是关键字,则用非递归遍历,判断谁的左右孩子时符合条件,然后确定f和p。
2、判断要删除结点p是否为NULL,为NULL返回false,说明没有该结点,否则继续
3、根据p的左右子树的情况:
(1)左右子树都不为空,循环找出p左子树的最右端结点(p的直接前驱s,和s的父节点q.s替到p.若q==p,则将s的左子树挂在p的左子树上,若q!=p,则s的左子树挂在q的右子树上,释放s,返回true。
(2)若左右子树有一个不为空(包括了都为空),则p=p->lchild/rchild;然后判断p时根结点,还是父结点的左子树,还是父结点的右子树。注意此时的p已经指向了他的左/右孩子,需要用q与f的孩子比较。然后将p挂到相应的位置上.删除q,返回true。