二叉排序树
二叉排序树又称为二叉查找树。它要么是一棵空树,要么是具有下列性质的二叉树:
(1)若它的左子树不为空,则左子树上所有结点的值均小于它的根结点的值。
(2)若它的右子树不为空,则右子树上所有结点的值均大于它的根结点的值。
(3)它的左右子树也分别为二叉排序树。
二叉排序树的查找:通常使用递归的方法来查找,即先查找根结点,若关键字与根结点的值相等,则找到,若关键字小于该根结点的值,则在左子树中继续查找,若关键字大于该根结点的值,则在右子树中查找。
typedef struct BiTNode
{
int data; //结点值
struct BiTNode* lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;
//查找:查找二叉排序树T中是否存在key
//指针f指向T的双亲,其初始值为NULL,若查找成功,则指针p指向该数据元素的结点,并返回TRUE,否则指针p指向查找路线上访问的最后一个结点并返回FALSE
bool SearchBST(BiTree T,int key,BiTree f,BiTree *p)
{
//查找不成功
if(!T)
{
*p = f;
return false;
}
//查找成功
else if(key == T->data)
{
*p = T;
return true;
}
else if(key < T->data)
{
//如果key值小于当前根结点的值,则继续在左子树中查找
return SearchBST(T->lchild,key,T,p);
}
else
{
//如果key值大于当前根结点的值,则继续在右子树中查找
return SearchBST(T->rchild,key,T,p);
}
}
二叉排序树的插入:所谓的二叉树的插入,其实就是将关键字放到树中合适的位置而已。
//当二叉排序树T中不存在关键字等于key的数据时,插入key并返回TRUE,否则返回FALSE
bool InsertBST(BiTree* T,int key)
{
BiTree p,s;
//若查找不成功
if(!SearchBST(*T,key,NULL,&p))
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = NULL;
s->rchild = NULL;
if(!p)
{
*T = s; //插入s为新的根结点
}
else if(key < p->data)
{
p->lchild = s; //插入s为左孩子
}
else
{
p->rchild = s; //插入s为右孩子
}
return true;
}
//若书中已有与关键字相同的结点,则插入失败
else
{
return false;
}
}
二叉排序树的删除:对于二叉树的删除,需要考虑的问题比较多,因为删除涉及到的问题很复杂,需要分三种情况考虑,即要删除的是叶子结点、要删除的结点只有左子树或只有右子树、要删除的结点左右子树都有。
对于要删除的结点只有左子树或只有右子树的情况,删除该结点后,将它的左子树或者右子树整个移动到删除了的结点的位置即可,可理解为“子承父业”。
对于要删除的结点左右子树都有的情况,解决方法比较麻烦。比较好的方法就是先找到要删除的结点p的直接前驱s,用s来替换结点p,然后再删除此结点s。
//删除
//若二叉排序树T中存在关键字等于key的数据元素,则删除该结点,并返回TRUE,否则返回FALSE
bool DeleteBST(BiTree* T,int key)
{
//如果不存在关键字等于key的结点
if(!*T)
{
return false;
}
else
{
if(key == (*T)->data)
{
return Delete(T);
}
//若关键字小于根结点的值
else if(key < (*T)->data)
{
return DeleteBST(&(*T)->lchild,key);
}
else
{
return DeleteBST(&(*T)->rchild,key);
}
}
}
//从二叉排序树中删除结点p,并重新连接它的左右子树
bool Delete(BiTree* p)
{
BiTree q,s;
//如果右子树为空,则只需要重新连接它的左子树
if((*p)->rchild == NULL)
{
q = *p;
*p = (*p)->lchild;
free(q);
}
//如果左子树为空,则只需要重新连接它的右子树
if((*p)->lchild == NULL)
{
q = *p;
*p = (*p)->rchild;
free(q);
}
//左右子树均不空
else
{
q = *p;
s = (*p)->lchild;
//转左,然后向右到尽头,找待删除结点的前驱
while(s->rchild)
{
q = s;
s = s->rchild;
}
(*p)->data = s->data; //s指向被删除结点的直接前驱
if(q != *p)
{
q->rchild = s->lchild; //重新连接q的右子树
}
else
{
q->lchild = s->lchild; //重新连接q的左子树
}
free(s);
}
return true;
}
总结:二叉排序树是以链接的方式存储,保持了链式存储结构在执行插入或删除操作时不用移动元素的优点,只要找到合适的插入和删除位置之后,仅需修改链接指针即可。插入删除的时间性能比较好,而对于二叉排序树的查找,走的就是从根结点到要查找的结点的路径,其比较次数等于关键字的结点在二叉排序树中的层数,至少为一次,最多也不会超过二叉树的深度。也就是说,二叉排序树的查找性能取决于二叉排序树中形状。如果二叉排序树是比较平衡的,即其深度与完全二叉树相同,均为,那么查找的时间复杂度也就为O(logn),近似于折半查找。
平衡二叉树(Self-Balancing Binary Search Tree 或 Height-Balanced Binary Search Tree)
平衡二叉树是一种二叉排序树,也可称AVL树,其中每一个结点的左子树和右子树的高度差至多为1。从平衡二叉树的英文名字可以看出,平衡二叉树是一种高度平衡的二叉排序树。意味着平衡二叉树要么是一棵空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的高度之差的绝对值不超过1。我们把二叉树上结点的左子树深度减去右子树深度的值称为平衡因子,那么平衡二叉树上所有结点的平衡因子只能是-1、0、1。也就是说,只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。以距离插入结点最近的,平衡因子的绝对值大于1的结点为根结点的子树,称为最小不平衡树。
平衡二叉树的实现原理:平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,如果是,则找出最小不平衡树。在保持二叉排序树特性的前提下,调整最小不平衡树中各结点之间的链接关系 ,进行相应的旋转,使之成为新的平衡子树。
平衡二叉树的实现算法:关于平衡二叉树的实现,需要注意的是左旋和右旋操作。所谓左旋,就是当某结点的平衡因子为-2时,将整个树进行逆时针旋转。右旋即指当某个结点的平衡因子为2时,将整个树进行顺时针旋转。
//树的结点结构
typedef struct BiTNode
{
int data; //结点值
int bf; //平衡因子
struct NiTNode *lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;
//右旋
void Right_Rotate(BiTree* p)
{
BiTree L;
L = (*p)->lchild; //L指向p的左子树根结点
(*p)->lchild = L->rchild; //L的右子树挂接为p的左子树
L->rchild = (*p);
*p = L; //p指向新的根结点
}
//左旋
void Right_Rotate(BiTree* p)
{
BiTree R;
R = (*p)->rchild; //R指向p的右子树根结点
(*p)->rchild = R->lchild; //R的左子树挂接为p的右子树
R->lchild = (*p);
*p = R; //p指向新的根结点
}
右旋代码解读:当传入一个二叉排序树P,将它的左孩子结点定义为L,