平衡二叉树是一颗合理的二叉排序树(BST):
二叉排序树失衡时,会退化成链表。导致查找的复杂度变为O(n)。
为了解决该问题,将二分法 + 二叉排序树 = 平衡二叉树
平衡二叉树定义(AVL):它或者是一颗空树,或者具有以下性质的二叉排序树:
它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,
且它的左子树和右子树都是一颗平衡二叉树。
一棵AVL树有如下必要条件:
条件一:它必须是二叉查找树。
条件二:每个节点的左子树和右子树的高度差至多为1。
(平衡:节点均匀分布在左右两侧,减少了数的深度也就减少了查找的次数)
保持平衡
AVL树的查找、插入、删除操作在平均和最坏的情况下都是O(logn),这得益于它时刻维护着二叉树的平衡。
不平衡的二叉查找树在查找时的效率是很低的,因此,AVL如何维护二叉树的平衡是我们的学习重点。
AVL树的平衡调整
如何构建一颗平衡二叉树
1.本质上与构建二叉排序树一致
2.在该过程中,如果发现树不符合特性,需要进行调整,调整需要用到树的高度
所以我们节点的结构体当中需要加一个字段来标识当前树的高度
3.调整方法:
LL、RR、LR、RL
1.找到失衡树的根节点root
2.找到导致失衡的节点node,
3.判断node在root的哪一侧,判断node在root孩子的哪一侧
RR:
1 2
\ / \
2 1 3
/ \ \
4 3 4
root=1、node=3、child=2
取中间的节点child,使它的父亲成为它的左孩子,如果它有左孩子的话,
那么这个左孩子连接到父亲的右孩子上
root->rchild = child->lchild;
child->lchild = root;
LL:
5 4
/ / \
4 3 5
/ \ /
3 6 6
root=5、node=3、child=4
取中间的节点,使它的父亲成为它的右孩子,如果它有右孩子的话,
那么这个右孩子连接到父亲的左孩子上
root->lchild = node->rchild;
node->rchild = root;
eg(大小树失衡):
4 4
/ \ / \
3 5 2 5
/ / \
2 1 3
/
1
LR:
8 8
/ \ / \
7 9 6 9
/ / \
5 5 7
\
6
取最最后一个节点作为父节点,将它的父亲作为自己的左孩子,将它父亲的父亲作为自己的右孩子
如果它有左孩子或者右孩子的话,它原先的左孩子,连接到父亲的右孩子
RL:
1 6
\ / \
8 1 8
/
6
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode
{
// 数据域
int data;
// 节点高度
int height;
// 左子树指针地址
struct TreeNode* lchild;
// 右子树指针地址
struct TreeNode* rchild;
}TreeNode;
int getHeight(TreeNode* node)
{
return node ? node->height : 0;
}
int max(int a, int b)
{
return a > b ? a : b;
}
// 传入中间节点的父节点node
void rrRotation(TreeNode* node, TreeNode** root)
{
// 中间节点temp
TreeNode* temp = node -> rchild;
// 如果它有左孩子的话,那么这个左孩子连接到父亲的右孩子上
node->rchild = temp -> lchild;
// 取中间的节点child,使它的父亲成为它的左孩子
temp->lchild = node;
// 把中间节点变为根节点,根节点指针指向新的根节点
*root = temp;
node->height = max(getHeight(node -> lchild), getHeight(node -> rchild)) + 1;
temp->height = max(getHeight(temp -> lchild), getHeight(temp -> rchild)) + 1;
}
void llRotation(TreeNode* node, TreeNode** root)
{
TreeNode* temp = node -> lchild;
node -> lchild = temp -> rchild;
temp -> rchild = node;
*root = temp;
node -> height = max(getHeight(node -> lchild), getHeight(node -> rchild)) + 1;
temp -> height = max(getHeight(temp -> lchild), getHeight(temp -> rchild)) + 1;
}
// 传入结构体指针的地址
void avlInsert(TreeNode** T, int data)
{
// 第一次根节点未初始化时进来,创建左右子树时也会进来
if (*T == NULL)
{
// 给结构体指针指向的变量开辟空间
*T = (TreeNode*)malloc(sizeof(TreeNode));
(*T) -> data = data;
(*T) -> height = 0;
(*T) -> lchild = NULL;
(*T) -> rchild = NULL;
}
else if(data < (*T)->data)// 当前插入的节点在当前节点的左侧
{
// 传入当期节点左子树指针的地址
avlInsert(&(*T)->lchild, data);
// 拿到当前节点左右子树的高度
int lHeight = getHeight((*T) -> lchild);
int rHeight = getHeight((*T) -> rchild);
// 判断高度差
if (lHeight - rHeight == 2)
{
// 判断node在root孩子的哪一侧
if (data < (*T)->lchild->data)
{
// LL 调整
llRotation(*T, T);
}
else
{
// LR 调整
rrRotation((*T) -> lchild, &(*T) -> lchild);
llRotation(*T, T);
}
}
}
else if(data > (*T)->data)// 当前插入的节点在当前节点的右侧
{
// 传入当期节点右子树指针的地址
avlInsert(&(*T) -> rchild, data);
// 拿到当前节点左右子树的高度
int lHeight = getHeight((*T) -> lchild);
int rHeight = getHeight((*T) -> rchild);
// 判断高度差
if (rHeight - lHeight == 2)
{
// 判断node在root孩子的哪一侧
if (data > (*T)->rchild->data)
{
// RR 调整
rrRotation(*T, T);
}
else
{
// RL 调整
llRotation((*T) -> rchild, &(*T) -> rchild);
rrRotation(*T, T);
}
}
}
// 左右子树最大的高度+1
(*T)->height = max(getHeight((*T) -> lchild), getHeight((*T) -> rchild)) + 1;
}
void preOrder(TreeNode* T)
{
if(T)
{
// 先访问根节点,再访问左子树,最后访问右子树
printf("%d ", T->data);
preOrder(T->lchild);
preOrder(T->rchild);
}
}
int main()
{
// 结构体指针
TreeNode* T = NULL;
int nums[5] = {1,8,6,7,10};
for (int i = 0; i < 5; i++)
{
// 传入结构体指针的地址
avlInsert(&T, nums[i]);
}
preOrder(T);
printf("\n");
}
/*
BST:
1
\
8
/
6
\
7
\
10
AVL:
6
/ \
1 8
/ \
7 10
preorder:6 1 8 7 10
*/