一、定义
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
从二叉查找树的定义中可以知道,二叉查找树实际上是一棵数据域有序的二叉树,即对树上的每个结点,都满足其左子树上的所有结点的数据域均小于或等于根结点的数据域,其右子树上的所有结点的数据域均大于根结点的数据域。
二、二叉查找树的基本操作
1. 插入
对一棵二叉查找树来说,查找某个数据域的结点一定是沿着确定的路径进行的。因此当对某个需要查找的值在二叉查找树中查找成功,说明结点已经存在;反之,如果这个需要查找的值在二叉查找树中查找失败,那么说明查找失败的地方一定是结点需要插入的地方。在 root == NULL 时新建需要插入的结点。
void insert(node* &root,int data){ //向树中插入结点(注意参数root 要加引用&)
if(root == NULL){ //空树,说明查找失败,也即插入的位置
root = newNode(data);
return;
}
if(data < root->data){ //要插入的结点权值小于当前结点的权值,将给结点插入到当前结点的左子树上
insert(root->lchild,data);
}else{
insert(root->rchild,data); //插入到右子树
}
}
node* newNode(int x){ //新建一个结点
node* Node = new node;
Node->data = x;
Node->lchild = Node->rchild = NULL;
return Node;
}
2. 建树
建立一棵二叉查找树,就是先后插入 n 个结点的过程,这和一般的建树是完全一样的。
node* create(int data[],int n){ //建立二叉树
node* root = NULL; //新建根结点 root
for(int i = 0;i < n;i++){
insert(root,data[i]); // 将 data[0]~data[n-1] 插入到儿茶搜索树中
}
return root;
}
3. 搜索
基本思路:
如果当前根结点 root 为空,说明查找失败,返回。
如果需要查找的值 x 等于当前根结点的数据域 root->data ,说明查找成功,访问之。
如果需要查找的值 x 小于当前根结点的数据域 root->data ,说明应该往左子树查找,因此 向 root->lchild 递归。
如果需要查找的值 x 大于当前根结点的数据域 root->data ,说明应该往右子树查找,因此 向 root->rchild 递归。
void search(node* root,int x){ //查找权值为 x 的结点
if(root == NULL){
cout<<"查找失败!"<<endl;
return;
}
if(x == root->data){
cout<<root->data<<endl;
}else if(x < root->data){
search(root->lchild,x);
}else{
search(root->rchild,x);
}
}
4. 删除
删除二叉查找树中的某个结点后,该树任然是一棵二叉查找树。
基本思路:
- 如果当前结点为空,说明不存在给定权值的结点,直接返回。
- 如果当前结点的权值恰为给定的结点权值 x ,说明找到了想要删除的结点,此时进入删除操作。
- 如果当前结点 root 不存在左右孩子,说明是叶子结点,直接删除。
- 如果当前结点 root 存在左孩子,那么在左子树中寻找前驱 pre ,然后让 pre 的数据覆盖 root,接着在左子树中删除 pre。
- 如果当前结点 root 存在右孩子,那么在右子树中寻找后继 next ,然后让 next 的数据覆盖 root,接着在右子树中删除 next。
- 如果当前结点的权值小于给定的权值 x,则在左子树中递归删除权值为 x 的结点。
- 如果当前结点的权值大于给定的权值 x,则在右子树中递归删除权值为 x 的结点。
示例代码:
void deleteNode(node* &root,int x){ //在二叉搜索树中删除权值为 x 结点
if(root == NULL) return; //不存在权值为 x 的结点
if(root->data == x){ //找到欲要删除的结点
if(root->lchild == NULL && root->rchild == NULL){ //叶子结点直接删除
root = NULL; //吧root地址设为NULL,父结点就引用不到它了
free(root);
}else if(root->lchild != NULL){ //左子树补位空时
node* pre = findMax(root->lchild); //找root的前驱
root->data = pre->data; //用前驱覆盖root
deleteNode(root->lchild,pre->data); //在左子树中删除前驱结点 pre
}else{ //右子树不为空时
node* next = findMin(root->rchild); //找root的后继
root->data = next->data; //用后继覆盖root
deleteNode(root->rchild,next->data); //在右子树中删除后继结点
}
}else if(x < root->data){
deleteNode(root->lchild,x);
}else{
deleteNode(root->rchild,x);
}
}
node* findMax(node* root){ //查找权值最大的结点
while(root->rchild){
root = root->rchild;
}
return root;
}
node* findMin(node* root){ //查找权值最小的结点
while(root->lchild){
root = root->lchild;
}
return root;
}
三、二叉查找树的性质
二叉查找树的一个实用性质:对二叉查找树进行中序遍历,遍历的结果是有序的。
练手题:PAT1043