一、二叉查找树的基本定义:
以前刷题接触过二叉查找树,这次做一个基础的总结。
所谓二叉查找树,就是一棵有序的二叉树(该树可以为空)。对于任意一个节点,如果该节点有左子树或者右子树,对于左子树上的所有节点,其值(称为数据域)都小于该节点;该节点的右子树上的所有值(称为数据域)也都大于该节点。
如下图所示。
对于二叉查找树来说,其本身为二叉树的变种,所以同样可以对二叉查找树进行相应的构建和操作。
二叉查找树的基本操作:
对于二叉查找树的基本操作有查找、插入、建树,和删除四种。
1.查找操作:
和普通二叉树相似,查找肯定是从根节点开始的。假定二叉查找树是采用动态存储,所以对于节点的判定情况有以下几种:
(1)如果当前root为空,为空树
(2)如果当前的节点值和查找值相同,找到;
(3)由于二叉查找树节点数据域有序,如果当前节点值小于查找值,查询右子树;
(4)由于二叉查找树节点数据域有序,如果当前节点值大于查找值,查询左子树;
以上的操作都为递归进行;
void search(node* root,int x){
if(root==NULL){
printf("empty tree");
return;
}
if(x==root->data){
printf("%d",root->data);
}else if(x<root->data){
search(root->lchild,x);
}else{
search(root->rchild,x);
}
}
二、插入操作:
对于插入操作,和二叉树的插入也有类似。
二叉查找树的查找操作,建立在查找基础之上。当查找一个值的情况下,由于二叉查找树有序,所以必然会按高度依次查找。对于查找结果,同样有两种。如果找到,则无需插入,因为已经有该值存在。如果没有查找到,则该位置必定是要插入值的地方。所以插入操作建立在查找操作root=NULL的地方。又因为是逐层查找,所以时间复杂度为O(h),其中h为二叉查找树的高度。
void insert(node* &root,int x){
if(root==NULL){
root=newNode(x);
return;
}
if(x==root->data){
return;
}else if(x<root->data){
insert(root->lchild,x);
}else{
insert(root->rchild,x);
}
}
三、二叉查找树的建立:
对于建立来说,也是利用insert函数进行相应的操作。但是由于没有进行平衡处理,所以对于相同树的不同序列,构建出来的树也不尽相同。
node* Create(int data[],int n){
node* root=NULL;
for(int i=0;i<n;i++){
insert(root,data[i]);
}
四、二叉查找树的删除:
由于二叉查找树是有序的,删除其中一个节点必定会导致有序序列的破坏,因此必须在删除之后对二叉查找树进行处理,重新使得节点有序。
如果在二叉查找树中删除一个节点,后续操作往往有两种:
其一,将比该节点权值小的最大节点覆盖,然后删除覆盖的节点;
其二,将比该节点权值大的最小节点覆盖,然后删除覆盖的节点;
在二叉查找树中,比该节点权值大的最小节点称为该节点的后继,比该节点权值小的最大节点称为该节点的前驱;利用前驱和后继就可以进行删除节点的替换。
寻找函数如下所示,两个函数的功能分别为寻找一个节点下的最大权值节点和最小权值节点,gai:
node* findMax(node* root){
while(root->rchild!=NULL){
root=root->rchild;
}
return root;
}
node* findMin(node* root){
while(root->lchild!=NULL){
root=root->lchild;
}
return root;
}
所以完整的删除流程如下所示:
(1)如果当前节点为空,返回,无法删除;
(2)如果当前节点是要找的节点,进行删除前的判定:
如果不存在左右孩子,则为叶节点,直接删除;
如果当前节点有左孩子,则寻找该节点的前驱pre,将pre覆盖该节点,然后删除pre;
如果当前节点有右孩子,则寻找该节点的后继next,将next覆盖该节点,然后删除next;
(3)如果当前节点不是要找的节点,进行左右子树判定,判定方式和查找相同;
void deleteNode(node* &root,int x){
if(root==NULL)
return;
if(root->data==x){
if(root->lchild==NULL&&root->rchild==NULL){
root=NULL;
}else if(root->lchild!=NULL){
node* pre=findMax(root->lchild);
root->data=pre->data;
deleteNode(root->lchild,pre->data);
//使用delete函数递归删除root->lchild中的pre节点;
}else {
node* next=findMin(root->rchild);
root->data=next->data;
deleteNode(root->rchild,next->data);
//使用delete函数递归删除root->lchild中的pre节点;
}
}else if(root->data>x){
deleteNode(root->lchild,x);
}else{
deleteNode(root->rchild,x);
}
}
当然,删除的时候也可以不进行递归,直接删除,如果pre和next不是叶子节点,直接将其左右子树按在上面即可。
这样删除的缺点是左右子树高度不平衡,所以需要平衡来避免退化成一条链。