包含的操作:
创建树,清空,搜索二叉树插入节点,返回树的高度,返回树的最大值,返回树的最小值,返回某个节点的父节点,删除某个节点,四种旋转操作LL、RR、LR、RL。
平衡二叉树的简介:
平衡二叉树是一种特殊的二叉搜索树(即每个节点的左子树的任何元素都比当前节点元素的值小,而右子树的任何元素都比当前节点元素的值大),但普通的二叉树在某些极端的情况下,会退化成近乎链状,如下图1中和图2中的两颗树的元素个数相等,但是图2中的树的查找效率明显将高于图1中的树。
图1 图2
为了降低这种查找的时间复杂度的预期,提出了平衡二叉树的概念,即树中的每个节点的左右子树的高度差的绝对值不超过1,这种高度差称为平衡因子(平衡因子=左子树高度减去右子树的高度)。由此图1中即是一棵平衡的二叉树,而图2不是,因为它的根节点的平衡因子大小为-2。
通过观察我们可以发现,只要将图2中的树向左“弯折”,即使图1的结果(如图3),而这种操作是可以在计算机中通过代码来实现的,以下是具体的流程讲解和代码实现。
在此说明,平衡二叉树的算法有多种,本文只讲有关的AVL树。
一些基本或辅助性质的二叉树操作
//二叉树的节点
struct BiTreeNode
{
int data;
BiTreeNode *Lchild;
BiTreeNode *Rchild;
};
//将树清空
void MakeEmpty(BiTreeNode *root)
{
if (root != NULL)
{
MakeEmpty(root->Lchild);
MakeEmpty(root->Rchild);
free(root);
}
return;
};
//二叉树的高度
int BiTreeHeight(BiTreeNode *root)
{
int right, left;
BiTreeNode *p = root;
if (p != NULL)
{
left = BiTreeHeight(p->Lchild);
right = BiTreeHeight(p->Rchild);
if (left >= right)
return left + 1;
else
return right + 1;
}
else
return 0;
}
//二叉树的创建
BiTreeNode* CreateTree(BiTreeNode *root)
{
int i;
scanf("%d",&i);
if (i != 0)
{
root = (BiTreeNode*)malloc(sizeof(BiTreeNode));
root->data = i;
root->Lchild = CreateTree(root->Lchild);
root->Rchild = CreateTree(root->Rchild);
return root;
}
else
return NULL;
}
//二叉树的先序遍历
void PreTravelOrder(BiTreeNode *root)
{
printf("%d ",root->data);
if (root->Lchild!=NULL)
PreTravelOrder(root->Lchild);
if (root->Rchild!=NULL)
PreTravelOrder(root->Rchild);
}
//二叉树的最大值:此写法不能传入空树
int findMax(BiTreeNode *root)
{
BiTreeNode *p = root;
while (p->Rchild != NULL)
{
p = p->Rchild;
}
return p->data;
}
//二叉树的最小值:此写法不能传入空树
int findMin(BiTreeNode *root)
{
BiTreeNode *p = root;
while (p->Lchild != NULL)
{
p = p->Lchild;
}
return p->data;
}
//树中插入元素(此段代码的插入符合二叉搜索树,但并未处理插入后失衡的情况)
BiTreeNode* Insert(int a, BiTreeNode *root)
{
BiTreeNode *p,*pi;
pi = root;
p = (BiTreeNode*)malloc(sizeof(BiTreeNode));
p->data = a;
p->Lchild = NULL;
p->Rchild = NULL;
if (root == NULL)
{
printf("This is a null tree!\n");
return p;
}
while (1)
{
if (a == root->data)
{
printf("The element is already exist\n");
free(p);
return pi;
}
else if (a < root->data)
{
if (root->Lchild == NULL)
{
root->Lchild = p;
return pi;
}
else
{
root = root->Lchild;
continue;
}
}
else if (a > root->data)
{
if (root->Rchild == NULL)
{
root->Rchild = p;
return pi;
}
else
{
root = root->Rchild;
continue;
}
}
}
return pi;
}
//找到某个节点的父节点
BiTreeNode* FindFather(BiTreeNode *son,BiTreeNode *root)
{
BiTreeNode *p = son, *pi;
pi = (BiTreeNode*)malloc(sizeof(BiTreeNode));
pi = root;
if (root == NULL)//传入的是空树
{
printf("Error\n");
return NULL;
}
if (son == root)//查找的子节点是树的根节点
{
printf("\nNo Father Node!\n");
return NULL;
}
if ((root->Lchild != NULL) && (root->Rchild = NULL))//传入的树只有一个节点
{
printf("The tree only one Node!\n");
return NULL;
}
while (1)
{
if ((pi->Lchild->data == son->data) || (pi->Rchild->data == son->data))
{
return pi;
}
else if (son->data<pi->data)//必然位于其左子树
{
if (pi->Lchild == NULL)
{
printf("No Father Node!\n");
return NULL;
}
else
pi = pi->Lchild;
}
else if (son->data>pi->data)//必然位于其右子树
{
if (pi->Rchild == NULL)
{
printf("No Father Node!\n");
return NULL;
}
pi = pi->Rchild;
}
}
}
四种旋转操作:RR、LL、RL、LR
LL、RR旋转
如上图中所展示的便是一种RR旋转,其基本概念是:平衡被破坏的节点(5)是右子树高度大于左子树,且导致平衡破坏的节点(7)是位于(5)的右子树的右子树上,类似的,我们如图4可以推导出LL旋转的规律。
(图片中节点上数字为节点的平衡因子大小)
代码中实际的基本操作为:
1. 传入不平衡节点A
2. 记录下A、B、E三个节点。
3. 将B的右儿子替换为A,再将A的右儿子替换为E。
4. 返回新的根节点B。
RR旋转的操作与此十分类似,在此不多做赘述。
以下是两种旋转的代码
//LL旋转
BiTreeNode* LL(BiTreeNode *blance)
{
BiTreeNode *temp, *blances_Lchild;
//先记录下需要平衡节点的左子树 和 左子树的右子树
blances_Lchild = blance->Lchild;
temp = blances_Lchild->Rchild;
//将需要平衡的节点挂在左子树的右子树上,原本的右子树被覆盖,保存在temp中
blances_Lchild->Rchild = blance;
//再将temp挂在需要平衡节点的左子树上
blance->Lchild = temp;
return blances_Lchild;
}
//RR旋转
BiTreeNode* RR(BiTreeNode *blance)
{
BiTreeNode *temp, *blances_Rchild;
//先记录下平衡节点的右子树 和 右子树的左子树
blances_Rchild = blance->Rchild;
temp = blances_Rchild->Lchild;
//将需要平衡的节点挂在右子树的左子树上,原本的左子树被覆盖,保存在temp中
blances_Rchild->Lchild = blance;
//再将temp挂在需要平衡节点的右子树上
blance->Rchild = temp;
return blances_Rchild;
}
RL、LR旋转
双旋转本质上是两次单旋转的结合。
例如LR旋转的过程:先将需要平衡的节点的左节点的右子树进行一次RR旋转,右旋过后的树变成了需要LL旋转的树,再LL旋转一次即可。
如图5
(图片中节点上数字为节点的平衡因子大小)
代码中实际的基本操作为:
1. 对A的左子树B进行RR旋转。
2. 对A本身进行LL进行LL旋转。
RL旋转同理
以下是两种旋转的代码
//LR旋转
BiTreeNode* RL(BiTreeNode *root)
{
//先左单旋,后右单旋
BiTreeNode *temp = root;
root->Rchild = LL(root->Rchild);
root = RR(root);
return root;
}
//RL旋转
BiTreeNode* LR(BiTreeNode *root)
{
//先右单旋,后左单旋
BiTreeNode *temp = root;
root->Lchild = RR(root->Lchild);
root = LL(temp);
return root;
}
最后,删除操作
由于每次删除都可能引起树的不平衡,所以必须对树的节点在递归中进行逐级旋转,以下是详细代码,具体操作见注释
//删除
BiTreeNode* Detele(BiTreeNode *root,int x)
{
BiTreeNode *temp = root,*temp2;
//查找的结果是空,返回原树
if (root==NULL)
{
printf("Element do not find\n");
return root;
}
//x位于其左子树里,在左子树里进行查找
if (x < temp->data)
{
root->Lchild = Detele(root->Lchild, x);
//删除过后进行旋转,先判断其左子树在删除后是否失去平衡
if (BiTreeHeight(temp->Rchild) - BiTreeHeight(temp->Lchild) == 2)
{
//再判断需要何种旋转方式
//由于是右子树大于左子树,所以只能是RL或RR旋
if (((root->Rchild->Lchild) != NULL) && (BiTreeHeight(root->Rchild->Lchild)>BiTreeHeight(root->Rchild->Rchild)))
{
root = RL(root);
}
else
root = RR(root);
}
return root;
}
//x位于其右子树里,在右子树里进行查找
else if (x > temp->data)
{
root->Rchild = Detele(root->Rchild, x);
if (BiTreeHeight(temp->Lchild) - BiTreeHeight(temp->Rchild) == 2)
{
//删除过后进行旋转,先判断其右子树在删除后是否失去平衡
//由于是左子树大于右子树,所以只能是LR或RR旋
if (((root->Lchild->Rchild) != NULL && (BiTreeHeight(root->Lchild->Rchild) > BiTreeHeight(root->Lchild->Lchild))))
{
root = LR(root);
}
else
root = LL(root);
}
return root;
}
//x如果恰好等于该节点的值
else
{
//有两个儿子时,,解决方式是从其右儿子中查找一个最小值进行替换
if (root->Lchild != NULL && root->Rchild != NULL)
{
//找到右子树中的最小值
while (temp->Lchild != NULL)
temp = temp->Lchild;
//将最小值赋给这个节点
root->data = temp->data;
//找到最小节点的父节点,并删除最小节点
temp2 = FindFather(temp,root);
temp2->Lchild = NULL;
free(temp);
//删除后该子树的根节点可能不平衡,需要进行旋转
if (BiTreeHeight(temp->Lchild) - BiTreeHeight(temp->Rchild) == 2)
{
//删除过后进行旋转,先判断其右子树在删除后是否失去平衡
//由于是左子树大于右子树,所以只能是LR或RR旋
if (((root->Lchild->Rchild) != NULL && (BiTreeHeight(root->Lchild->Rchild) > BiTreeHeight(root->Lchild->Lchild))))
{
root = LR(root);
}
else
root = LL(root);
}
}
//只有一个或者没有儿子时
else
{
//没有左儿子,此时可能只有右儿子,或者没有儿子,无论右儿子是否为空,只需要将右儿子拉上来即可
if (root->Lchild == NULL)
root = root->Rchild;
//此时同理
else if (root->Rchild == NULL)
root = root->Lchild;
}
return root;
}
}
如若有错,欢迎指正留言。
如有雷同,那当时看的博客多半就是您的。