二叉搜索树概念
什么是二叉搜索树,在我看来二叉搜索树相对于二叉树最大的区别在于:二叉搜索树中的任意节点x,其左节点的关键字都小于x.key,其右节点的关键字都大于x.key。由该性质可以对二叉搜索树有三种遍历方法。
先序遍历:先输出根节点的关键字,而后输出左子树的关键字,最后输出右子书的关键字。
中序遍历:先输出左子树的关键字,而后输出根节点的关键字,最后输出右子树的关键字。
后序遍历:先输出左子树的关键字,而后输出右子树的关键字,最后输出根节点的关键字。
如下图所示:
先序遍历:7,3,18,10,8,11,22,26
中序遍历:3,7,8,10,11,18,22,26
后序遍历:3,8,11,10,26,22,18,7
查询二叉树
二叉树的查询包括关键字查询,最大值、最小值查询,前驱和后驱的查询,其中关键字,最大值、最小值查询比较简单,重点介绍一下后驱的查询方法。
查找二叉树节点x的后驱会出现如下三种情况:
1.当节点x存在右孩子时,我们只需要查找以右孩子为树节点的树,关键字最小的节点,该节点则为节点x的后驱。
2.当节点x右孩子为空时,且该节点x为父节点的左孩子,则节点x的后驱为该父节点。
3.当节点x右孩子为空时,且该节点x为父节点的右孩子,则节点x的后驱为上层祖先。
伪代码如下:
TREE-SUCCESSOR(x)
if x.right!=NUL
return TREE_MINIMUM(x.right)
y=x.p
while y!=NULL and x==y.right
x=y
y=y.p
return y
tree_node* tree_successed(tree_node *x) //查找节点x的后驱
{
if(x->right!=NULL) //情况一,假设存在右子树
{
return tree_min(x->right); //查找右子树的最小值
}
while(x->parent->left!=x) //情况2、3,假设没有右子树,则为父节点或者上层父节点
{
x=x->parent;
}
return x->parent;
}
插入和删除
插入操作相对于删除操作简单,插入只需要找到关键字所处的位置,并修改相应指针即可,具体代码如下:
void tree_insert(tree_node *x,tree_node *z) //二叉树插入一个节点
{
tree_node *y=NULL;
while(x!=NULL)
{
y=x;
if(x->data>=z->data)
{
x=x->left;
}
else
{
x=x->right;
}
}
z->parent=y;
if(y->data>z->data)
{
y->left=z;
}
else
{
y->right=z;
}
z->left=NULL;
z->right=NULL;
}
对于删除操作,是二叉搜索树的重点,也需要分成四种情况讨论:
1.节点x无孩子,即x为叶节点,直接将其用空节点替换就可以。
2.节点x存在一个孩子,则直接将孩子替换该节点,并修改相应节点属性。
3.节点x两个孩子都存在,但是节点x的后驱节点为节点x的右孩子,由二叉搜索树的性质可以知道后驱节点肯定没有左孩子,故直接将节点x的右孩子替换节点x,并修改相应节点(左孩子节点,父节点)的属性。
4.节点x两个孩子都存在,但后驱节点不为节点x的孩子,则通过后驱查找函数找到该节点y,由于后驱节点y没有左孩子,则可以用后驱节点y的右孩子替换后驱节点y,后驱节点y替换待删除节点x,并修改相应节点的属性。
具体情况如下所示:
情况1:
情况2:
情况3:
情况4:
上图中所有删除节点都为z
删除具体代码如下:
void tree_transplant(tree_node *u,tree_node *v) //节点v代替节点u
{
if(u->parent->left==u)
{
u->parent->left=v;
}
else
{
u->parent->right=v;
}
if(v!=NULL) //防止指针v为空
{
v->parent=u->parent;
}
/*if(u->left!=NULL) //容易自己指向自己,造成死循环
{
v->left=u->left;
u->left->parent=v;
}
if(u->right!=NULL)
{
v->right=u->right;
u->right->parent=v;
}*/
}
void tree_delete(tree_node *x,tree_node *y) //删除节点y
{
tree_node *z,*successed;
z=tree_search(x,y->data);
if(z->left==NULL) //左孩子为空
{
tree_transplant(z,z->right);
}
else if(z->right==NULL) //右孩子为空
{
tree_transplant(z,z->left);
}
else //存在两个孩子
{
if(z->right->left==NULL) //情况3
{
tree_transplant(z,z->right);
z->right->left=z->left;
z->left->parent=z->right;
}
else //情况4
{
successed=tree_successed(z);
tree_transplant(successed,successed->right);
successed->right->left=successed->left; //将左孩子的属性更正
successed->left->parent=successed->right;
tree_transplant(z,successed);
successed->left=z->left;
z->left->parent=successed;
}
}
}