7.平衡二叉树
平衡二叉树(Balanced Binary Tree),又称AVL树(Adelson-Velskii and Landis Tree),是一种特殊的二叉排序树。。其主要特点是任何节点的两个子树的高度最大差别为1(也可以是空树),即每个节点的 左子树和右子树的高度差至多等于1。这种性质确保了树的高度保持相对较低,从而提高了查找、插入和 删除操作的效率。因为树的本质是为了帮助提高查找、插入和删除操作的效率,如果树不平衡,那么在 根节点的左右两边元素数量差过大,则体现不出提升效率
平衡二叉树(右子树高度比左子树大1)
非平衡二叉树(右子树高度比左子树大2)
)
7.1 平衡二叉树的插入
平衡二叉树在插入时有可能会改变平衡因子而导致不平衡。 因为新插入节点而导致平衡性被破坏的节点也叫麻烦节点,而被其破坏平衡的节点叫被破坏节点。
所有结果和经验,一共把破坏平衡型的类型分为了四种,分别是LL型、RR型、LR型和RL型 (L=left,R=right)。
7.1.1 LL型
在平衡二叉树的左孩子的左子树中插入新节点。
此处不管麻烦节点是左孩子节点-6 还是麻烦节点是右孩子节点-4 都是LL型,因为都是在平衡二叉树的左孩子的左子树中插入新节点
//下列场景为LL型触发右旋
//递归插入
insertNodeRec(&root, -3);
insertNodeRec(&root, 3);
insertNodeRec(&root, -5);
insertNodeRec(&root, -1);
//层次遍历
levelPrint(root);
insertNodeRec(&root, -4);
//层次遍历
levelPrint(root);
为了恢复平衡二叉树的平衡性,需要进行右旋(单旋)右上旋转操作。
- 将“被破坏节点”的左子节点 提升为新的根节点
- 将“被破坏节点”变为 被破坏节点的左子节点 的右子节点
- 如果“被破坏节点”的左子节点有右子树,则将这个右子树设置为“被**破坏节点”**的左子树
以被破坏节点为基础进行左旋,保留被破坏节点的左右孩子之间的连接性
// 右旋
void rightNode(TreeNode **root)
{
// 先分析位置变化 被破坏的根节点、根节点的左孩子(-3)和破环节点的左孩子的右子树(-1)发生变化 root就是0不用找
// 根节点的左孩子(-3)
TreeNode *leftTree= (*root)->left;
// 和破环节点的左孩子的右子树(-1)
TreeNode *leftTreeRight = leftTree->right;
// 指行旋转(和逻辑顺序变一下,如果先把让左子节点变成根节点,那么就会找不到之前的根节点0,那就要在定义一个节点指针)
//1. 将“被破坏节点”变为 被破坏节点的左子节点 的右子节点
leftTree = (*root);//root和leftTree都指向根节点
// 将“被破坏节点”0变为 被破坏节点的左子节点-3 的右子节点
leftTree->right = (*root);
// 2“被破坏节点”的左子节点的右子树-1设置为“被破坏节点”0的左子树
(*root)->left = leftTreeRight;
// 分析高度变化-》根据辅助高度函数左右字数变化高度就可能变化,没有变化一定不会变化 被破坏节点0 和 被破坏节点”的左子节点-1发生变化,因为左右字数变了
// 被破坏节点”的左子节点的右子树-1位置遍历但是左右子树没有变,所以高度没有变
// 原根节点0高度受到影响
(*root)->height = maxHeight( (*root)->left, (*root)->right) + 1;//+1 因为自己也有高度
// 原根节点左子树高度受到影响
leftTree->height = maxHeight( leftTree->left, leftTree ->right) + 1;
// 3.更新根节点
*root= leftTree;
7.1.2 RR型
在平衡二叉树的右孩子的右子树中插入新节点
代码
// 下列场景RR型触发左旋
// 递归插入
insertNodeRec(&root, -3);
insertNodeRec(&root, 3);
insertNodeRec(&root, 1);
insertNodeRec(&root, 4);
// 层次遍历
levelPrint(root);
insertNodeRec(&root, 5);
// 层次遍历
levelPrint(root);
为了恢复平衡二叉树的平衡性,需要进行**左旋(单旋)**左上旋转操作。
-
将“被破坏节点”的右子节点 提升为新的根节点
-
将“被破坏节点”变为 被破坏节点的右子节点 的左子节点
-
如果“被破坏节点”的右子节点有左子树,则将这个左子树设置为“被破坏节点”的右子树
-
问题1:插入和删除里面写的旋转条件
-
插入函数 举个LL型的 if(balance > 1 && data < (*p_tree)->left->data) 其他一样道理
- 平衡因子>1表示是被破坏的左孩子, 插入的值小于左孩子的值表示是在左孩子的左子树中插入新节点(小于左孩子就在左孩子的左子树插入值)
-
删除函数
-
LL型:if(balance > 1 && getBalance((*root)->left) >= 0)
平衡因子>1表示是被破坏的左孩子 被破坏的左孩子 的平衡因子大于等于0是因为balance 大于1或者小于-1时,整个树已经失去平衡,不再是AVL树,必须进行旋转操作,不管子节点的平衡因子是多少,都需要调整树的结构来恢复平衡 >=0和<=0 是判断是左旋还是右旋(插入也是一样)。
-
-
VL树的平衡操作中,LL和RR类型的判断与LR和RL类型的判断条件是互补的
7.3完整代码
#include <iostream>
#include <cstdlib>
#include <queue>
using namespace std;
// 定义二叉树的节点结构体
typedef struct TreeNode
{
int data; //节点存储的数据
int height; //记录树的高度
struct TreeNode *left; // 指向左子节点的指针
struct TreeNode *right; // 指向右子节点的指针
}TreeNode;
// 创建一个新的二叉树节点
TreeNode *createNode(int value)
{
TreeNode *newTreeNode = new TreeNode{value};
if(!newTreeNode)
{
perror("创建失败");
exit(-1);
}
//创建出来的节点左右孩子指向空,高度是1
newTreeNode->left = nullptr;
newTreeNode->right = nullptr;
newTreeNode->height = 1;
return newTreeNode;
}
// 获取树的高度
int getHeight(TreeNode *root)
{
if(!root)
{
return 0;
}
return root->height;
}
// 计算树的高度,获取两个节点种较高的
int maxHeight(TreeNode *a,TreeNode *b)
{
//a为空
if(!a)
{
// b也是空或者b不是空
return b == nullptr ? 0 :b->height;
}
//b为空
else if(!b)
{
// a不为空
return a->height;
}
// ab都不是空 返回高度大的
return a->height > b->height ? a->height : b->height;
}
// 右旋 针对LL型
void rightNode(TreeNode **root)
{
if (!(*root))
{
return;
}
// 先分析位置变化 被破坏的根节点、根节点的左孩子(-3)和破环节点的左孩子的右子树(-1)发生变化 root就是0不用找
// 根节点的左孩子(-3)
TreeNode *leftTree= (*root)->left;
// 和破环节点的左孩子的右子树(-1)
TreeNode *leftTreeRight = leftTree->right;
// 指行旋转(和逻辑顺序变一下,如果先把让左子节点变成根节点,那么就会找不到之前的根节点0,那就要在定义一个节点指针)
//1. 将“被破坏节点0”变为 被破坏节点的左子节点-3 的右子节点
leftTree->right = (*root);
// 2“被破坏节点”的左子节点的右子树-1设置为“被破坏节点”0的左子树
(*root)->left = leftTreeRight;
// 分析高度变化-》根据辅助高度函数左右字数变化高度就可能变化,没有变化一定不会变化 被破坏节点0 和 被破坏节点”的左子节点-1发生变化,因为左右字数变了
// 被破坏节点”的左子节点的右子树-1位置遍历但是左右子树没有变,所以高度没有变
// 原根节点0高度受到影响
(*root)->height = maxHeight( (*root)->left, (*root)->right) + 1;//+1 因为自己也有高度
// 原根节点左子树高度受到影响
leftTree->height = maxHeight( leftTree->left, leftTree ->right) + 1;
// 3.更新根节点
*root= leftTree;
}
// 左旋 针对RR型
void leftNode(TreeNode **root)
{
if (!(*root))
{
return;
}
// 根节点的右孩子(3)
TreeNode *rightTree = (*root)->right;
// 根节点的右孩子的左子树(1)
TreeNode *rightTreeLeft = rightTree->left;
// 旋转
// 1.将“被破坏节点0”变为 被破坏节点的右子节点 3的左子节点
rightTree->left = *root;
// 2.被破坏节点”的右子节点的左子树1设置为“被破坏节点”的右子树
(*root)->right = rightTreeLeft;
// 原根节点0高度受到影响
(*root)->height = maxHeight((*root)->left, (*root)->right);
// 被破坏节点的右子节点 3高度受到影响
rightTree->height= maxHeight(rightTree->left, rightTree->right);
// 3.更新根节点
*root = rightTree;
}
///获取节点的平衡因子
int getBalance(TreeNode *root)
{
if(!root)
{
return 0;
}
//左子树高度 - 右子树高度
return getHeight(root->left) - getHeight(root->right);
}
//在有序二叉树里插入节点(递归方式)
void insertNodeRec(TreeNode **p_tree, int data)
{
// 节点为空,直接插入
if(!*p_tree)
{
*p_tree = createNode(data);
return;
}
// 值小于当前父节点的值,遍历左子树 *(*p_tree)->left访问左子树的数据
if( data < (*p_tree)->data)
{
insertNodeRec(&(*p_tree)->left,data);
}
// 值大于当前父节点的值,遍历右子树
else if(data > (*p_tree)->data)
{
insertNodeRec(&(*p_tree)->right,data);
}
// 值已经存在
else
{
return ;
}
// 每次添加节点都会增加树的高度
(*p_tree)->height = maxHeight((*p_tree)->left, (*p_tree)->right) + 1;
// 检查是否平衡并且修复
// 获取平衡因子
int balance = getBalance(*(p_tree));
// LL 平衡因子>1表示是被破坏的左孩子 插入的值小于左孩子的表示是左孩子的左子树中插入新节点(小于左孩子就在左孩子的左子树插入值)
if(balance > 1 && data < (*p_tree)->left->data)
{
// 右旋
rightNode(p_tree);
return;
}
// RR
else if(balance < -1 && data > (*p_tree)->right->data)
{
// 左旋
leftNode(p_tree);
return;
}
// LR
else if(balance > 1 && data > (*p_tree)->left->data)
{
//被破坏节点的左孩子左旋变成LL
leftNode(&(*p_tree)->left);//意思是先吧当前节点的二级指针解引用得到一级指针,然后指向左孩子,
// 再把左孩子的地址给函数函数的参数是二级指针,需要传递一级指针的地址
// 右旋
rightNode(p_tree);
return;
}
// RL
else if(balance < - 1 && data < (*p_tree)->right->data)
{
// 被破坏节点的右孩子左旋右旋成RR
rightNode(&(*p_tree)->right);
// 左旋
leftNode(p_tree);
return;
}
}
// 先序遍历(根左右)
void frontPrint(TreeNode *p_tree)
{
if(!p_tree)
{
return;
}
// 遍历根节点
cout.width(2);
cout << p_tree->data << " ";
// 遍历左子树
frontPrint(p_tree->left);
// 遍历右子树
frontPrint(p_tree->right);
}
// 中序遍历(根左右)
void milldePrint(TreeNode *p_tree)
{
if(!p_tree)
{
return;//这个 return 并不会结束整个递归过程,只会结束当前栈帧的执行
// 在递归遍历中,返回到上一个栈帧后,程序会继续执行后续代码,
}
// 遍历左子树
milldePrint(p_tree->left);
// 遍历根节点
cout.width(2);
cout << p_tree->data << " ";
// 遍历右子树
milldePrint(p_tree->right);
}
// 后序遍历(左右根)
void backPrint(TreeNode *p_tree)
{
if(!p_tree)
{
return;//这个 return 并不会结束整个递归过程,只会结束当前栈帧的执行
// 在递归遍历中,返回到上一个栈帧后,程序会继续执行后续代码,
}
// 遍历左子树
backPrint(p_tree->left);
// 遍历右子树
backPrint(p_tree->right);
// 遍历根节点
cout.width(2);
cout << p_tree->data << " ";
}
// 层级遍历
void levelPrint(TreeNode *p_tree)
{
if(!p_tree)
{
cout << "空" << endl;
return;
}
// 创建队列
queue <TreeNode*> q;
// 先存储根节点
q.push(p_tree);//p_tree是第一个节点根节点
while(!q.empty())
{
// 存储一下第一个节点,别把链表断了
TreeNode *current = q.front();
// 当前节点出队
q.pop();
// 访问当前节点 输出值
cout.width(2);
cout << current->data <<" ";
if(current->left)
{
q.push(current->left );
}
if(current->right)
{
q.push(current->right );
}
}
// 队列最终会变空,因为所有节点都会被弹出且没有新的节点会被加入
cout << endl;
}
// 辅助函数,用于找到树中的最小节点
TreeNode *findMin(TreeNode *root)
{
// 最小节点就是要找的节点左子树的左子树号,一直找左子树
while(root->left)
{
root= root ->left;
}
return root;
}
//删除节点
void deleteNode(TreeNode** root, int key)
{
if( *root == nullptr)
{
return;
}
// 如果要删除的节点在左子树中 递归遍历
if(key < (*root)->data)
{
deleteNode(&(*root)->left, key);
}
// 如果要删除的节点在右子树中 递归遍历
// 必须用else if这样会完全分开,如果只是if会对同一个节点进行多次不必要的递归调用
else if(key > (*root)->data)
{
deleteNode(&(*root)->right, key);
}
// 知道删除的节点
// 如果是叶子节点
else
{
if((*root)->left == nullptr &&(*root)->right ==nullptr)
{
delete *root;
*root = nullptr;
return;
}
// 如果有一个左孩子节点
else if((*root)->right ==nullptr)
{
TreeNode *tmp = *root;
// 左子节点替换根节点
*root = (*root)->left;
// 删除原来根节点内容
delete tmp;
tmp = nullptr;
return;
}
// 如果有一个右孩子节点
else if((*root)->left ==nullptr)
{
TreeNode *tmp = *root;
// 左子节点替换根节点
*root = (*root)->right;
// 删除原来根节点内容
delete tmp;
tmp = nullptr;
return;
}
// 如果节点有两个子节点,找到右子树中的最小节点(或左子树中的最大节点)
//先找到最小节点
else
{
TreeNode *minNode = findMin((*root)->right);
// 把里面的值替换
(*root)->data = minNode->data;
// 删除右子树中的最小节点
deleteNode(&(*root)->right,minNode->data );
// 直接删除最小节点
// delete minNode;
// minNode = nullptr; error因为只更新了左右孩子信息,没有更新父节点信息
}
}
// 树为空
if(!root)
{
return;
}
//
// 每次添加节点都会增加树的高度
(*root)->height = maxHeight((*root)->left, (*root)->right) + 1;
// 检查是否平衡并且修复
// 获取平衡因子
int balance = getBalance(*(root));
// LL 平衡因子>1表示是被破坏的左孩子 被破坏的左孩子 的平衡因子大于等于0
if(balance > 1 && getBalance((*root)->left) >= 0)
{
// 右旋
rightNode(root);
return;
}
// RR
else if(balance < -1 && getBalance((*root)->left) < 0 )
{
// 左旋
leftNode(root);
return;
}
// LR
else if(balance > 1 && getBalance((*root)->left) < 0)
{
//被破坏节点的左孩子左旋变成LL
leftNode(&(*root)->left);//意思是先吧当前节点的二级指针解引用得到一级指针,然后指向左孩子,
// 再把左孩子的地址给函数函数的参数是二级指针,需要传递一级指针的地址
// 右旋
rightNode(root);
return;
}
// RL
else if(balance < - 1 && getBalance((*root)->left) < 0 )
{
// 被破坏节点的右孩子左旋右旋成RR
rightNode(&(*root)->right);
// 左旋
leftNode(root);
return;
}
}
void deintTree(TreeNode *root)
{
if(!root)
{
return;
}
deintTree(root->left);
deintTree(root->right);
delete root;
root = nullptr;
}
int main()
{
// 创建节点
TreeNode* root = createNode(0);
// 下列场景为LL型触发右旋
// //递归插入
// insertNodeRec(&root, -3);
// insertNodeRec(&root, 3);
// insertNodeRec(&root, -5);
// insertNodeRec(&root, -1);
// //层次遍历
// levelPrint(root);
// insertNodeRec(&root, -4);
// //层次遍历
// levelPrint(root);
//下列场景RR型触发左旋
//递归插入
// insertNodeRec(&root, -3);
// insertNodeRec(&root, 3);
// insertNodeRec(&root, 1);
// insertNodeRec(&root, 4);
// // 层次遍历
// levelPrint(root);
// insertNodeRec(&root, 5);
// //层次遍历
// levelPrint(root);
//下列场景为LR型
// //递归插入
// insertNodeRec(&root, -3);
// insertNodeRec(&root, 3);
// insertNodeRec(&root, -4);
// insertNodeRec(&root, -2);
// //层次遍历
// levelPrint(root);
// insertNodeRec(&root, -1);
// //层次遍历
// levelPrint(root);
// 下列场景为RL型
// //递归插入
// insertNodeRec(&root, -3);
// insertNodeRec(&root, 3);
// insertNodeRec(&root, 2);
// insertNodeRec(&root, 4);
// //层次遍历
// levelPrint(root);
// insertNodeRec(&root, 1);
// //层次遍历
// levelPrint(root);
// deintTree(root);
// root = NULL;
//下列场景删除节点后为RL型
//递归插入
insertNodeRec(&root, -3);
insertNodeRec(&root, 3);
insertNodeRec(&root, 2);
insertNodeRec(&root, 4);
insertNodeRec(&root, -4);
insertNodeRec(&root, 1);
//层次遍历
levelPrint(root);
deleteNode(&root, -4);
//层次遍历
levelPrint(root);
deintTree(root);
root = NULL;
return 0;
}