二叉搜索树的性质:对于以i为根节点的子树,其右子树中所有节点的关键值都不小于根节点的关键值,左子树中所有节点的关键值都小于根节点的关键值。大部分在二叉搜索树中的操作都与树的高度成正比。下面依次介绍一些二叉树的操作。
首先给出二叉搜索树的节点定义:
///BST的节点
typedef struct node
{
node *p; ///指向父节点的指针
node *left; ///指向左儿子的指针
node *right; ///指向右儿子的指针
int key; ///节点附加的信息
}N,*BSTree;
一。BST的中序遍历
由BST的性质,中序遍历可以从小到大输出所有元素
思想:我们只需要递归的先打印左子树,然后打印根节点,然后打印右子树就行了
递归边界是:如果某个根节点为空,那么我们返回打印它的根节点。
///BST中序遍历函数,传入根节点指针
///可以非递减的打印出BST中的元素
void InorderTreeWalk(BSTree x)
{
if(x==NULL)
return;
InorderTreeWalk(x->left); ///递归打印左子树
print(x); ///之所以使用一个函数进行打印是因为有可能我们需要打印的不只是一个数据
InorderTreeWalk(x->right);
}
二。BST查找函数
只需要根据BST性质,安装关键字的大小关系一直往下查询即可。
///BST查找函数,传入根节点指针和待查找的k
///如果k在BST中,我们返回包含k的节点,否则返回nulll
BSTree BSTSearch(BSTree x,int k)
{///思想:根据BST的性质,我们不断往下走,直到找到k或者是走到一个空节点
if(x==NULL) ///走到了空节点,表示BST中不包含k值,返回null
return NULL;
if(x->key==k) ///找到了,返回当前节点的指针
return x;
if(k<x->key) ///向左走
return BSTSearch(x->left,k);
else ///向右走
return BSTSearch(x->right,k);
}
下面给一个非递归版本的,其实只要在递归中不涉及回溯,就很容易写出非递归的版本。
BSTree BSTSearch1(BSTree x,int k)
{
while(x!=NULL&&x->key!=k) ///边界条件
{
if(k<x->key)
x=x->left;
else
x=x->right;
}
return x;
}
三。查找BST中最小和最大的关键字
按照BST的性质,只需要一直往左走/往右走即可。
///传入根节点,返回指向具有最小/最大关键字节点的指针
BSTree BSTMinNum(BSTree x)
{ ///查找最大关键字位置,只需要一直往左走,直到某个左儿子为空的节点
while(x->left!=NULL)
x=x->left;
return x;
}
BSTree BSTMaxNum(BSTree x)
{ ///查找最小关键字位置,只需要一直往右走,直到某个右儿子为空的节点
while(x->right!=NULL)
x=x->right;
return x;
}
四。查询一个节点在中序遍历下的后继节点
思想:后继就是所有大于x中最小的元素。如果一个节点有右子树的话
那么后继就是其右子树中最小的元素。如果没有,那么我们就要向上找到第一个比其大的祖先,即第一个将该节点所在子树作为左子树的节点。
///输入一个节点x,输出在中序遍历下其后继(如果该节点具有最大的关键字,输出null)
BSTree BSTSuccessor(BSTree x)
{
if(x->right!=NULL) ///有右子树
return BSTMinNum(x->right);
BSTree y=x->p; ///没有右子树
while(y!=NULL&&y->right==x)
{
x=y;
y=y->p;
}
return y;
}
五。BST插入函数
在BST中插入一个节点,首先要按性质找到可以插入的位置。
///在BST中插入一个节点z
///传入根节点和z
void BSTInsert(BSTree &rt,BSTree z)
{///如果当前树为空,那么我们将z作为根节点
///否则我们按照二叉查找树的性质找到可以可以插入的节点进行插入,在此构成中必须记录父节点
BSTree y=NULL; ///插入节点的父节点
BSTree x=rt;
while(x) ///寻找可以插入的节点
{
y=x;
if(z->key>x->key)
x=x->right;
else
x=x->left;
}
if(y==NULL) ///如果树为空,则将z作为根节点
rt=z;
else if(z->key>y->key) ///z作为y的右节点
y->right=z;
else ///z作为y的左节点
y->left=z;
z->p=y; ///最后记得修改插入节点的父节点信息
}
六。BST删除函数
删除函数是所有操作中最复杂的一个,需要根据不同的情况作出不同的反应。分几种情况处理:
1:如果x没有左右儿子,直接改变x父节点的相应指针就行了
2:如果x只有左/右儿子,用x的左/右儿子替代掉x。1,2在代码中是统一处理的
3:如果x左右儿子都存在,我们需要找到x的后继y,然后再按这个后继y是否为x的右儿子进行分类
(1):如果y是x的右儿子,我们用y替代x并且修改y的左儿子为x的左儿子
(2):如果y不是x的左儿子,我们用y的右儿子替代掉y然后再用y替换掉x
///v为根节点的子树替代掉u为根节点的子树,但是只是更新u的父节点信息
///传入根节点rt 节点u 节点v
void TransPlant(BSTree &rt,BSTree &u,BSTree &v)
{///如果传入的节点v为空则是将u删除掉
if(u->p==NULL) ///如果u是根节点
rt=v;
else if(u->p->right==u) ///如果u是其父节点的右儿子,更新右儿子信息
u->p->right=v;
else
u->p->left=v;
if(v!=NULL) ///更新u的父节点信息
v->p=u->p;
}
///二叉树节点删除函数,删除掉传入的节点x
///传入根节点rt 待删除的节点x
void BSTreeDelet(BSTree &rt,BSTree x)
{
if(x->left==NULL) ///左儿子为空,用右儿子替代x
TransPlant(rt,x,x->right);
else if(x->right==NULL) ///右儿子为空,用左儿子替代x
TransPlant(rt,x,x->left);
else
{
BSTree y=BSTSuccessor(x); ///找x的后继
if(y!=x->right) ///y不是x的右儿子
{
TransPlant(rt,y,y->right); ///y的右儿子代替y
///将x的右子树的根节点换成y
y->right=x->right;
x->right->p=y;
}
TransPlant(rt,x,y); ///y替代x
y->left=x->left; ///令y指向x的左子树
x->left->p=y;
}
}
下面给出一份完整的测试代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
///BST的节点
typedef struct node
{
node *p; ///指向父节点的指针
node *left; ///指向左儿子的指针
node *right; ///指向右儿子的指针
int key; ///节点附加的信息
}N,*BSTree;
void print(BSTree x)
{
printf("%d ",x->key);
}
///BST中序遍历函数,传入根节点指针
///可以非递减的打印出BST中的元素
void InorderTreeWalk(BSTree x)
{
if(x==NULL)
return;
InorderTreeWalk(x->left); ///递归打印左子树
print(x); ///之所以使用一个函数进行打印是因为有可能我们需要打印的不只是一个数据
InorderTreeWalk(x->right);
}
///BST查找函数,传入根节点指针和待查找的k
///如果k在BST中,我们返回包含k的节点,否则返回nulll
BSTree BSTSearch(BSTree x,int k)
{
if(x==NULL) ///走到了空节点,表示BST中不包含k值,返回null
return NULL;
if(x->key==k) ///找到了,返回当前节点的指针
return x;
if(x->key>k) ///向左走
return BSTSearch(x->left,k);
else ///向右走
return BSTSearch(x->right,k);
}
///下面给一个非递归版本的
///其实只要在递归中不涉及回溯,就很容易写出非递归的版本
BSTree BSTSearch1(BSTree x,int k)
{
while(x!=NULL&&x->key!=k) ///边界条件
{
if(k<x->key)
x=x->left;
else
x=x->right;
}
return x;
}
///查找BST中最小和最大的关键字
///传入根节点,返回指向具有最小/最大关键字节点的指针
BSTree BSTMinNum(BSTree x)
{ ///查找最大关键字位置,只需要一直往左走,直到某个左儿子为空的节点
while(x->left!=NULL)
x=x->left;
return x;
}
BSTree BSTMaxNum(BSTree x)
{ ///查找最小关键字位置,只需要一直往右走,直到某个右儿子为空的节点
while(x->right!=NULL)
x=x->right;
return x;
}
///前驱和后继
///输入一个节点x,输出在中序遍历下其后继(如果该节点具有最大的关键字,输出null)
BSTree BSTSuccessor(BSTree x)
{
if(x->right!=NULL) ///有右子树
return BSTMinNum(x->right);
BSTree y=x->p; ///没有右子树的情况
while(y!=NULL&&y->right==x)
{
x=y;
y=y->p;
}
return y;
}
///输入一个节点x,输出在中序遍历下其前继(如果其为最小元素则输出null)
///在BST中插入一个节点z
///传入根节点和z
void BSTInsert(BSTree &rt,BSTree z)
{
BSTree y=NULL; ///插入节点的父节点
BSTree x=rt;
while(x!=NULL) ///寻找可以插入的节点
{
//cout<<3<<endl;
y=x;
if(z->key>x->key)
x=x->right;
else
x=x->left;
}
if(y==NULL) ///如果树为空,则将z作为根节点
rt=z;
else if(z->key>y->key) ///z作为y的右节点
y->right=z;
else ///z作为y的左节点
y->left=z;
z->p=y; ///最后记得修改插入节点的父节点信息
}
///v为根节点的子树替代掉u为根节点的子树,但是只是更新u的父节点信息
///传入根节点rt 节点u 节点v
void TransPlant(BSTree &rt,BSTree &u,BSTree &v)
{///如果传入的节点v为空则是将u删除掉
if(u->p==NULL) ///如果u是根节点
rt=v;
else if(u->p->right==u) ///如果u是其父节点的右儿子,更新右儿子信息
u->p->right=v;
else
u->p->left=v;
if(v!=NULL) ///跟新u的父节点信息
v->p=u->p;
}
///二叉树节点删除函数,删除掉传入的节点x
///传入根节点rt 待删除的节点x
void BSTDelet(BSTree &rt,BSTree x)
{
if(x->left==NULL) ///左儿子为空,用右儿子替代x
TransPlant(rt,x,x->right);
else if(x->right==NULL) ///右儿子为空,用左儿子替代x
TransPlant(rt,x,x->left);
else
{
BSTree y=BSTSuccessor(x); ///找x的后继
if(y!=x->right) ///y不是x的右儿子
{
TransPlant(rt,y,y->right); ///y的右儿子代替y
///将x的右子树的根节点换成y
y->right=x->right;
x->right->p=y;
}
TransPlant(rt,x,y); ///y替代x
y->left=x->left; ///令y指向x的左子树
x->left->p=y;
}
}
int main()
{
BSTree rt=NULL; ///BST根节点指针,初始化为空
for(int i=10;i>=1;i--)
{
BSTree t = new N;
t->key=i;
t->left=t->right=t->p=NULL; ///要设置好这几个指针的初始值,否则会引发异常
BSTInsert(rt,t);
}
InorderTreeWalk(rt); ///二叉树的中序遍历
cout<<endl;
BSTree x=BSTMinNum(rt); ///查找二叉树中最小元素
cout<<x->key<<endl;
x=BSTSuccessor(x); ///输出最小元素的后继
if(x==NULL)
cout<<-1<<endl;
else
cout<<x->key<<endl;
x=BSTSearch(rt,6); ///查找BST中关键值为6的节点
BSTDelet(rt,x); ///删除掉节点x
if(x==NULL) ///节点不存在
cout<<"关键值为6的节点不存在"<<endl;
else
cout<<"找到了关键值为6的节点"<<endl;
InorderTreeWalk(rt);
cout<<endl;
return 0;
}