【二叉排序树(BST)树的不足】
现给定一组有序数列[1, 2, 3, 4, 5, 6],要求创建一颗二叉排序树(BST),并分析问题所在!
由数列[1, 2, 3, 4, 5, 6]构成的BST树,如上图所示。那么这棵树存在的问题有以下几点:
1、左子树全部为空,从形式上看,更像一个单链表,实际上已经退化成一个单链表了 (时间复杂度退化成了O(n))
2、对于插入、删除、修改速度是没有影响,但是这种结构查询效率会明显降低(因为需要依次比较)不能发挥BST的优势,因为每次还需要比较左子树,器查询速度比单链表还慢。
因此为了解决二叉排序树(BST)退化成链表的问题,出现了一种全新的树结构(AVL树),这种结构不仅能防止退化成单链表,还在插入,查找,删除得到了进一步的改善,所使用的时间复杂度和空间复杂度相较于其他树更低!
2、AVL树介绍
1、平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树,可以保证查询效率较高。
2、具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL算法、替罪羊树、Treap、伸展树等。
如上图左边就是一颗平衡二叉树(AVL树),需要注意的是:平衡二叉树必须是一颗二叉排序树(BST),在BST树的基础上满足左右两个子树的高度差的绝对值不超过 1。
平衡二叉树是一种特殊的二叉搜索树,它的特点是:
每个节点的左右子树的高度差不超过1,也就是说,任意节点的左右子树高度差不会超过1,这样可以保证树的高度不会太高,降低了查找、插入、删除操作的时间复杂度。
平衡二叉树的查找、插入、删除操作的时间复杂度都是O(log n),与树的高度相关,高度越低,时间复杂度越小。
平衡二叉树的实现方式有很多,比如AVL树、红黑树等,它们的性质略有不同,但都保证了树的平衡性,提高了树的性能。
平衡二叉树可以用于实现有序集合、有序映射等数据结构,也可以应用于数据库索引、文件系统等领域。
判断「平衡二叉树」
判断「平衡二叉树」的 2 个条件:
- 是「二叉排序树」
- 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)
(1)下图不是「平衡二叉树」因为它不是「二叉排序树」违反第 1 条件
(2)下图不是「平衡二叉树」因为有节点子树高度差大于 1 违法第 2 条件
(3)下图是「平衡二叉树」因为符合 1、2 条件
【平衡因子 BF】
定义:左子树和右子树高度差
计算:平衡因子=左子树高度 - 右子树高度的值
别名:简称 BF(Balance Factor 而不是 Boy Friend)
一般来说 BF 的绝对值大于 1,,平衡树二叉树就失衡,需要「旋转」纠正
【最小不平衡子树】
距离插入节点最近的,并且 BF 的绝对值大于 1 的节点为根节点的子树。
「旋转」纠正只需要纠正「最小不平衡子树」即可
【二种旋转方式】
左旋
旧根节点为新根节点的左子树
新根节点的左子树(如果存在)为旧根节点的右子树
右旋:
旧根节点为新根节点的右子树
新根节点的右子树(如果存在)为旧根节点的左子树
【4 种旋转纠正类型】
LL 型:插入左孩子的左子树,右旋
RR 型:插入右孩子的右子树,左旋
LR 型:插入左孩子的右子树,先左旋,再右旋
RL 型:插入右孩子的左子树,先右旋,再左旋
【LL 型失衡】【右旋】
第三个节点「1」插入的 时候,BF(3) = 2,BF(2) = 1 LL 型失衡,右旋,根节点顺时针旋转
(1)最小不平衡子树「右旋」
旧根节点(节点 3)为新根节点(节点 2)的右子树
新根节点的 右子树(如果存在)为旧根节点的左子树
【RR 型失衡】【左旋】
第三个节点「3」插入的 时候,BF(1)=-2 BF(2)=-1,RR 型失衡,左旋,根节点逆时针旋转
(1)最小不平衡子树左旋
旧根节点(节点 1)为新根节点(节点 2)的左子树
新根节点的左子树(如果存在)为旧根节点的右子树
【LR 型】
第三个节点「3」插入的 时候,BF(3)=2 BF(1)=-1 LR 型失衡,先「左旋」再「右旋」
(1)最小不平衡子树左子树 {2,1} 先左旋
旧根节点(节点 1)为新根节点(节点 2)的左子树
新根节点的左子树(如果存在)为旧根节点的右子树
(2)最小不平衡子树 {3,2,1} 再右旋
旧根节点(节点 3)为新根节点(节点 2)的右子树
新根节点的 右子树(如果存在)为旧根节点的左子树
【 RL 型】
第三个节点「1」插入的 时候,BF(1)=-2 BF(3)=1 RL 型失衡,先「右旋」再「左旋」
(1)最小不平衡子树根节点右子树{3,2}先右旋
旧根节点(节点 3)为新根节点(节点 2)的右子树
新根节点的 右子树(如果存在)为旧根节点的左子树
(2)最小不平衡子树 {1,2,3} 再左旋(L)
旧根节点(节点 1)为新根节点(节点 2)的左子树
新根节点的左子树(如果存在)为旧根节点的右子树
【实例】
接下来我们以 {3,2,1,4,5,6,7,10,9,8} 为实例练习刚刚的 4 种插入方式
(1)依次插入 3、2、1 插入第三个点 1 的时候 BF(3)=2 BF(2)=1,LL 型失衡。
对最小不平衡树 {3,2,1}进行「右旋」
(2)依次插入 4 ,5 插入 5 点的时候 BF(3) = -2 BF(4)=-1,RR 型失衡
对最小不平衡树 {3,4,5} 进行「左旋」
(3)插入 6 点的时候 BF(2)=-2 BF(4)=-1 ,RR 型失衡 对最小不平衡树进行「左旋」
旧根节点(节点 2)为新根节点(节点 4)的左子树
新根节点(节点 4)的 左子树(节点 3)为旧根节点的右子树
新根节点(节点 4)的左子树(节点 3)为旧根节点的右子树
(4)插入 7 节点的时候 BF(5)=-2, BF(6)=-1 ,RR 型失衡,对最小不平衡树 进行「左旋」
旧根节点(节点 5)为新根节点(节点 6)的左子树
新根节点的左子树(这里没有)为旧根节点的右子树
(5)依次插入 10 ,9 。插入 9 点的时候 BF(10) = 1,BF(7) = -2 ,RL 型失衡,对先「右旋」再「左旋」
右子树先「右旋」
最小不平衡子树的右子树 {10,9} 先右旋:
旧根节点(节点 10)为新根节点(节点 9)的右子树
新根节点(节点 9)的右子树(这里没有右子树)为旧根节点的左子树
最小不平衡子树再左旋:
旧根节点(节点 7)为新根节点(节点 9)的左子树
新根节点(节点 9)的左子树(这里没有左子树)为旧根节点的右子树
(6)最后插入节点 8 ,BF(6)=-2 BF(9)=1,RL 型失衡,先「右旋」再「左旋」
最小不平衡子树的右子树 {9,7,10,8} 先「右旋」
右旋:
旧根节点(节点 9 {9,10})为新根节点(节点 7)的右子树
新根节点(节点 7)的右子树(这里是 节点 8)为旧根节点(节点 9)的左子树
最小不平衡子树 {6,5,7,9,8,10} 再「左旋」
旧根节点(节点 6 {6,5} )为新根节点(节点 7)的左子树
新根节点的左子树(这里没有)为旧根节点的右子树
左旋结束
全部代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef struct node {
int data; //数据
node *left; //左指针
node *right; //右指针
int height; //其左右子树最大深度
} avlnode, * avltree;
avlnode *creat_node(int key, avlnode *left, avlnode *right) {
avlnode *node = new avlnode;
node->data = key;
node->left = left;
node->right = right;
node->height = 0;
return node;
}
//获取深度
int get_height(avlnode *node) {
//如果结点为空就返回0,如果不为空就返回它的深度
return node == NULL ? 0 : ((avlnode *)(node))->height;
}
//LL 左孩子的左子树(右旋)
avltree left_left_rotation(avltree tree) {
avlnode *k = tree->left;
tree->left = k->right;
k->right = tree;
tree->height = max(get_height(tree->left), get_height(tree->right)) + 1;
k->height = max(get_height(k->left), get_height(k->right)) + 1;
return k;
}
//RR 右孩子的右子树(左旋)
avltree right_right_rotation(avltree tree) {
avlnode *k = tree->right;
tree->right = k->left;
k->left = tree;
tree->height = max(get_height(tree->left), get_height(tree->right)) + 1;
k->height = max(get_height(k->left), get_height(k->right)) + 1;
return k;
}
//LR 左孩子的右子树(先左旋再右旋)
avltree left_right_rotation(avltree tree) {
tree->left = right_right_rotation(tree->left);
tree = left_left_rotation(tree);
return tree;
}
//RL 右孩子的左子树(先右旋再左旋)
avltree right_left_rotation(avltree tree) {
tree->right = left_left_rotation(tree->right);
tree = right_right_rotation(tree);
return tree;
}
//插入结点
avltree avltree_insertNode(avltree tree, int key) {
//判断当前结点是否为空,如果为空则创建结点
if (tree == NULL) {
avlnode *node = creat_node(key, NULL, NULL);
tree = node;
}
//开始递归寻找插入结点的位置
else if (key < tree->data) {
tree->left = avltree_insertNode(tree->left, key); //先递归插入结点
if (get_height(tree->left) - get_height(tree->right) == 2) {
//在这里判断是LL还是LR
if (key > tree->left->data) {
tree = left_right_rotation(tree);
} else {
tree = left_left_rotation(tree);
}
}
} else if (key > tree->data) {
tree->right = avltree_insertNode(tree->right, key); //先递归插入结点
if (get_height(tree->right) - get_height(tree->left) == 2) {
//在这里判断是RL还是RR
if (key < tree->right->data) {
tree = right_left_rotation(tree);
} else {
tree = right_right_rotation(tree);
}
}
} else {
cout << "不允许插入相同的值" << endl;
}
//回溯时,更新结点深度
tree->height = max(get_height(tree->left), get_height(tree->right)) + 1;
return tree;
}
//找到删除结点的前驱(和二叉排序树类似)
avlnode *mininum_node(avltree tree) {
if (tree == NULL) {
return NULL;
}
while (tree->right) {
tree = tree->right;
}
return tree;
}
//删除结点
avltree avltree_deleNode(avltree tree, int key) {
if (tree == NULL) {
cout << "没有该结点" << endl;
return tree;
}
//要删除的结点在左子树
if (key < tree->data) {
tree->left = avltree_deleNode(tree->left, key);
//判断是否失衡
if (get_height(tree->right) - get_height(tree->left) == 2) {
int RL = get_height(tree->right->left), RR = get_height(tree->right->right);
//判断属于哪种情况
if (RL > RR) {
tree = right_left_rotation(tree);
} else {
tree = right_right_rotation(tree);
}
}
}
//要删除的结点在右子树
else if (key > tree->data) {
tree->right = avltree_deleNode(tree->right, key);
//判断是否失衡
if (get_height(tree->left) - get_height(tree->right) == 2) {
int LR = get_height(tree->left->right), LL = get_height(tree->left->left);
//判断属于哪种情况
if (LR > LL) {
tree = left_right_rotation(tree);
} else {
tree = left_left_rotation(tree);
}
}
}
//找到了要删除的结点
else {
//如果要删除的结点有两个孩子
if (tree->left && tree->right) {
//找到删除结点左子树的最大值(和二叉排序树操作类似)
avlnode *min_node = mininum_node(tree->left);
tree->data = min_node->data; //改变要删除结点的值
tree->left = avltree_deleNode(tree->left, min_node->data); //找到下一个要删除的结点
}
//如果要删除的结点只有一个孩子或者没有孩子
else {
tree = tree->left ? tree->left : tree->right;
}
}
//更新结点深度
if (tree) {
tree->height = max(get_height(tree->left), get_height(tree->right)) + 1;
}
return tree;
}
//中序遍历
void in_order(avltree tree) {
if (tree) {
in_order(tree->left);
cout << tree->data << " ";
in_order(tree->right);
}
}
int main() {
avltree tree = NULL;
int a[] = { 1, 4, 3, 2, 9, 6, 7, 11, 10, 8 };
int length = sizeof(a) / sizeof(int);
for (int i = 0; i < length; i++) {
tree = avltree_insertNode(tree, a[i]);
}
in_order(tree);
cout << endl;
tree = avltree_deleNode(tree, 2);
in_order(tree);
cout << endl;
tree = avltree_deleNode(tree, 8);
in_order(tree);
}