二叉排序树的特性:
1、若左子树非空,则左子树上所有结点的值均小于根节点的值;
2、若右子树非空,则右子树上所有结点的值均大于根节点的值;
3、左右子树又分别是一颗二叉排序树;
综上,左子树结点的值<根结点的值<右子树结点的值。所以对二叉排序树进行中序遍历,可以得到一个递增的有序序列。
1、数据结构定义
typedef struct BSTNode{
int key;
struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;
2、二叉树的查找非递归
二叉树的查找是从根结点开始,逐层向下比较,若二叉排序树非空,关键字与根结点的值相比较,若相等,则查找成功,若大于,往右子树寻找,若小于,往左子树寻找。
//二叉排序树的非递归查找
BSTNode *BST_Search(BSTree T,int key)
{
while(T!=NULL&&T.key!=key) //树不空以及没找到关键字
{
if(T.key<key) //根结点小于关键字,左子树寻找
{
T=T->lchild;
}
else //否则右子树寻找
{
T=T->rchild;
}
}
return T;
}
非递归算法的空间复杂度是O(1)。
3、二叉排序树的递归查找
//二叉排序树的递归查找
BSTNode *BST_Search(BSTree T,int key)
{
if(T==NULL) //查找失败
return T;
if(T.key==key) //查找成功
{
return T;
}
else if(T.key>key)
return BST_Search(T->lchild,key);
else
return BST_Search(T->rchild,key);
}
但是递归查找,由于需要一个递归工作栈,所以其空间复杂度是树高,最坏情况就是一棵单子树,即时间复杂度为O(n)。
4、 二叉排序树的递归插入
由于二叉排序树中不存在两个值相等的结点,所以当插入时,发现插入的值与树中某个结点的值相等,则插入失败。
插入过程:
1、若二叉树为空,则直接插入结点;
2、否则,若关键字小于根结点的值,则插入到左子树中,若关键字大于根结点的值,则插入到右子树中。插入的结点一定是一个新添加的叶结点,且是查找失败时的查找路径上访问的最后一个结点的左孩子或右孩子。
//二叉排序树的插入(递归实现)
int BST_Insert(BSTree &T,int k)
{
if(T==NULL)
{
T=(BSTree)malloc(sizeof(BSTNode));
T->key=k;
T->lchild=T->rchild=NULL;
return 1; //插入成功
}
else if(T->key==k) //有重复值,插入失败
return 0;
else if(T->key<key)
return BST_Insert(T->rchild,k);
else
return BST_Insert(T->lchild,k);
}
其时间复杂度为:O(h),h是树高。
5、 二叉排序树的创建
二叉树的创建过程,其实就是一个不断插入的过程。
//二叉树的创建
void Creat_BST(BSTree &T,int a[],int n)
{
T=NULL; //初始时T为空
int i=0;
while(i<n)
{
BST_Insert(T,a[i]);
i++;
}
}
不同的关键字序列可能得到同款二叉排序树,也可能得到不同的额二叉排序树。
6、二叉排序树的删除
二叉排序树的删除过程:
1、若被删除的结点是叶子结点,则直接删除;
2、若被删除的结点a只有一颗左子树或者右子树,则让 a的子树占领a的位置,成为a的父节点的子树。
3、若结点a有左右子树,则让a的直接后继(直接前驱也一样)替代a,然后从二叉排序树中删除这个直接后继(直接前驱),接着就转换为1或2的情况处理。
a的直接后继:a的右子树中最左下的结点,该结点一定没有左子树,且该结点的值是右子树中最小的。
a的直接前驱:a的左子树中最右下的结点,该结点一定没有右子树,且该结点的值是左子树中最大的。
对于第三种情况使用的是直接后继结点替补的:
//二叉排序树的删除
int BST_Delete(BSTree &T,int key)
{
BSTree p=T;
BSTree pre=NULL; //指向要删除结点的前一个结点
while(p!=NULL&&p->key!=key) //先找到要删除的结点
{
if(key>p->key)
{
pre=p;
p=p->rchild;
}
else
{
pre=p;
p=p->lchild;
}
}
if(p==NULL) //要删除的结点不存在
return 0;
else
{
//第一种情况:删除叶子结点,直接删
if(p->lchild==NULL&&p->rchild==NULL)
{
if(pre->key<key)
{
pre->rchild=NULL;
}
else
{
pre->lchild=NULL;
}
}
//第二种情况:单子树
else if((p->lchild==NULL&&p->rchild)||(p->rchild==NULL&&p->lchild))
{
if(pre->key<key)
{
if(p->rchild) //右子树存在
{
pre->rchild=p->rchild;
}
else //左子树存在
{
pre->rchild=p->lchild;
}
}
else
{
if(p->rchild)
{
pre->lchild=p->rchild;
}
else
{
pre->lchild=p->lchild;
}
}
}
//第三种情况:左右子树都存在,这里我使用的是用直接后继填补(直接前驱一样,后面也给出)
else if(p->lchild&&p->rchild)
{
BSTree *pre1=p; //由于要删除的结点不需要动,只是被替换,所以不需要知道被删除结点的前驱,而是要知道被替换结点的前驱
BSTree *q=p->rchild;
while(q->lchild) //寻找右子树的最左下的结点
{
pre1=q;
q=q->lchild;
}
p->key=q->key; //替换结点值
if(p!=pre1) //右子树的左子树存在
{
pre1->lchild=q->lchild;
}
else //右子树的左子树不存在,用根替换的
{
pre1->rchild=q->rchild;
}
delete(q);
}
return 1; //删除成功
}
}
对于第三种情况使用的是直接前驱结点替补的:
//二叉排序树的删除
int BST_Delete(BSTree &T,int key)
{
BSTree p=T;
BSTree pre=NULL; //指向要删除结点的前一个结点
while(p!=NULL&&p->key!=key) //先找到要删除的结点
{
if(key>p->key)
{
pre=p;
p=p->rchild;
}
else
{
pre=p;
p=p->lchild;
}
}
if(p==NULL) //要删除的结点不存在
return 0;
else
{
//第一种情况:删除叶子结点,直接删
if(p->lchild==NULL&&p->rchild==NULL)
{
if(pre->key<key)
{
pre->rchild=NULL;
}
else
{
pre->lchild=NULL;
}
}
//第二种情况:单子树
else if((p->lchild==NULL&&p->rchild)||(p->rchild==NULL&&p->lchild))
{
if(pre->key<key)
{
if(p->rchild) //右子树存在
{
pre->rchild=p->rchild;
}
else //左子树存在
{
pre->rchild=p->lchild;
}
}
else
{
if(p->rchild)
{
pre->lchild=p->rchild;
}
else
{
pre->lchild=p->lchild;
}
}
}
//第三种情况:左右子树都存在,这里我使用的是用直接后继填补(直接前驱一样,后面也给出)
else if(p->lchild&&p->rchild)
{
BSTree *pre1=p; //由于要删除的结点不需要动,只是被替换,所以不需要知道被删除结点的前驱,而是要知道被替换结点的前驱
BSTree *q=p->lchild;
while(q->rchild) //寻找左子树的最右下的结点
{
pre1=q;
q=q->rchild;
}
p->key=q->key; //替换结点值
if(p!=pre1) //左子树的右子树存在
{
pre1->rchild=q->lchild;
}
else //左子树的右子树不存在,用根替换的
{
pre1->lchild=q->lchild;
}
delete(q);
}
return 1; //删除成功
}
}