二叉搜索树
二叉搜索树又称为二叉排序树,它要么是一颗空树,要么是一颗具有以下性质的树:
- 若他的左子树不为空,则左子树上所有节点的值都小于根结点的值
- 若他的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也满足二叉搜索树的性质
如下图所示,满足一个二叉搜索树:
对一个二叉搜索树做以下操作:
- 插入
- 查找
- 删除
插入
向一个二叉搜索树中插入一个元素:
需要考虑两种情况:向空树中插入一个元素以及向非空树中插入一个元素。
向非空树中插入元素需要考虑他将要插入的位置在哪里,必须保证插入之后还是要满足一个二叉搜索树的性质。
以下是采取递归方法实现的代码:
void SearchTreeInsert(SearchTreeNode** root, SearchTreeType key)
{
if(root==NULL)
{
return;
}
//空树的情况
if(*root==NULL)
{
SearchTreeNode* new_node=CreateNode(key);
*root=new_node;
return;
}
SearchTreeNode* to_find=*root;
if(key>to_find->key)
{
SearchTreeInsert(&to_find->rchild,key);
}
else if(key<to_find->key)
{
SearchTreeInsert(&to_find->lchild,key);
}
else
{
return;//默认不允许出现相同元素
}
}
以下是采取非递归方式的代码:
void SearchTreeInsertByLoop(SearchTreeNode** root, SearchTreeType key)
{
if(root==NULL)
{
return;
}
if(*root==NULL)
{
SearchTreeNode* new_node=CreateNode(key);
*root=new_node;
}
else
{
//先找到要插入的位置
//cur表示要插入的位置
//pre表示要插入的位置的父结点
SearchTreeNode* cur=*root;
SearchTreeNode* pre=NULL;
while(cur)
{
if(cur->key>key)
{
pre=cur;
cur=cur->lchild;
}
else if(cur->key<key)
{
pre=cur;
cur=cur->rchild;
}
else
{
break;
}
}
//此时已经可以插入了
if(pre->key>key)
{
pre->lchild=CreateNode(key);
}
else
{
pre->rchild=CreateNode(key);
}
}
}
查找
查找也是根据该二叉搜索树的性质进行的查找操作。首先与根节点比较,大于根节点就去它的右子树查找,小于根节点,就去它的左子树查找。
递归实现:
SearchTreeNode* SearchTreeFind(SearchTreeNode* root, SearchTreeType to_find)
{
if(root==NULL)
{
return NULL;
}
if(to_find>root->key)
{
return SearchTreeFind(root->rchild,to_find);
}
else if(to_find<root->key)
{
return SearchTreeFind(root->lchild,to_find);
}
else
{
return root;
}
}
SearchTreeNode* SearchTreeFindByLoop(SearchTreeNode* root, SearchTreeType to_find)
{
if(root==NULL)
{
return NULL;
}
SearchTreeNode* cur=root;
while(cur!=NULL)
{
if(cur->key>to_find)
{
cur=cur->lchild;
}
else if(cur->key<to_find)
{
cur=cur->rchild;
}
else
{
break;
}
}
return cur;
}
删除
删除一个二叉搜索树中元素实现起来需要考虑以下几种情况:
- 要删除的元素查找得到:
- 删除的元素无左右子树
- 删除的元素只有左子树
- 删除的元素只有右子树
- 删除的元素具有左右子树
- 要删除的元素查找不到
要删除的元素无左右子树:
要删除的元素只有左子树(右子树相同)
要删除的元素具有左右子树:
void SearchTreeRemoveByLoop(SearchTreeNode** root, SearchTreeType key)
{
if(root==NULL)
{
return;
}
if(*root==NULL)
{
return;
}
SearchTreeNode* to_remove=*root;
SearchTreeNode* parent=NULL;
//先找到删除的位置
while(to_remove!=NULL)
{
if(to_remove->key>key)
{
parent=to_remove;
to_remove=to_remove->lchild;
}
else if(to_remove->key<key)
{
parent=to_remove;
to_remove=to_remove->rchild;
}
else
{
break;
}
}
//找不到的情况
if(to_remove==NULL)
{
return;
}
//此时已经找到位置
//分情况讨论
//1.无左右子树
if(to_remove->lchild==NULL&&to_remove->rchild==NULL)
{
if(parent->key>key)
{
parent->lchild=NULL;
free(to_remove);
}
else
{
parent->rchild=NULL;
free(to_remove);
}
}
//2.只有左子树
else if(to_remove->lchild!=NULL&&to_remove->rchild==NULL)
{
parent->lchild=to_remove->lchild;
free(to_remove);
}
//3.只有右子树
else if(to_remove->lchild==NULL&&to_remove->rchild != NULL)
{
parent->rchild=to_remove->rchild;
free(to_remove);
}
//4.有左右子树
else
{
SearchTreeNode* min=to_remove->rchild;
while(min->lchild!=NULL)
{
parent=min;
min=min->lchild;
}
//此时找到了最小的结点
to_remove->key=min->key;
parent->lchild=min->rchild;
free(min);
}
}
void SearchTreeRemove(SearchTreeNode** root, SearchTreeType key)
{
if(root==NULL)
{
return;
}
if(*root==NULL)
{
return;
}
SearchTreeNode* cur=*root;
if(key>cur->key)
{
return SearchTreeRemove(&cur->rchild,key);
}
else if(key<cur->key)
{
return SearchTreeRemove(&cur->lchild,key);
}
else
{
//找得到删除的位置
//1.无左右子树
if(cur->lchild==NULL&&cur->rchild==NULL)
{
*root=NULL;
free(cur);
return;
}
//2.有左子树
else if(cur->lchild!=NULL&&cur->rchild==NULL)
{
*root=cur->lchild;
free(cur);
return;
}
//3.有右子树
else if(cur->lchild==NULL&&cur->rchild!=NULL)
{
*root=cur->rchild;
free(cur);
return;
}
//4.既有左子树又有右子树
else if(cur->lchild!=NULL&&cur->rchild!=NULL)
{
SearchTreeNode* min=cur->rchild;
while(min->lchild!=NULL)
{
min=min->lchild;
}
cur->key=min->key;
SearchTreeRemove(&cur->rchild,min->key);
return;
}
}
return;
}
二叉搜索树的性能
二叉搜索树不管是插入还是删除,都要先进行查找,查找的效率代表了二叉搜索树中各个操作的性能。
最优情况下,二叉搜索树为完全二叉树,时间复杂度:O(logN)
最坏情况下,二叉搜索树退化为单支树,时间复杂度:O(N)
因此,当二叉搜索树退化为单支树时,效率非常底下,查找元素相当于在顺序表中搜索元素,效率低下。
AVL
一颗AVL树或者是空树,或者是具有以下性质的二叉搜索树:左右子树都是AVL树。
并且左右子树的高度之差绝对值不超过1
对于一颗AVL树,平均搜索时间复杂度是:O(lgN)
红黑树
红黑树是一颗二叉搜索树,它在每个结点上增加了一个存储位来表示节点的颜色,可以是red或是black,通过对任何一条从根结点到叶子节点简单路径上的颜色来约束,红黑树可保证最长路径不超过最短路径的两倍,因而近似平衡。所以实际上,红黑树的性能比AVL树性能高。