当前位置: 首页 > news >正文

数据结构之平衡二叉树

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);}//创建出来的节点左右孩子指向空,高度是1newTreeNode->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;}// RLelse if(balance < - 1 && data < (*p_tree)->right->data){// 被破坏节点的右孩子左旋右旋成RRrightNode(&(*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表示是被破坏的左孩子 被破坏的左孩子 的平衡因子大于等于0if(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;}// RLelse if(balance < - 1 &&  getBalance((*root)->left) < 0 ){// 被破坏节点的右孩子左旋右旋成RRrightNode(&(*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;
}

相关文章:

  • 非对称加密算法(RSA、ECC、SM2)——密码学基础
  • 会话历史管理——持久化
  • 2.4 GHz频段的11个信道通过 5 MHz中心频率间隔 实现覆盖
  • 学习:困?
  • vue2和vue3组件如何监听子组件生命周期
  • 【AI面试准备】对新技术充满热情,具有较强的学习能力和独立解决问题的能力
  • 藏语英语中文机器翻译入门实践
  • c++_csp-j算法 (6)_高精度算法(加减乘除)
  • 多线程编程的常见问题
  • 深度理解linux系统—— 进程优先级
  • 柔性PZT压电薄膜多维力传感器在微创手术机器人的应用
  • 数字智慧方案6186丨智慧应急指挥解决方案(43页PPT)(文末有下载方式)
  • C++调试(贰):Dump文件的生成(附Qt示例)
  • 鼎讯信通【专注通信解决方案】
  • 销售总监求职简历模板
  • 开闭原则(OCP)
  • 数字智慧方案5869丨智慧健康医疗养老大数据整体规划方案(76页PPT)(文末有下载方式)
  • deepseek 技巧整理
  • 阿里通义千问 Qwen3 模型发布
  • Bootstrap(自助法)​​:无需假设分布的统计推断工具
  • 爱彼迎:一季度总收入约23亿美元,将拓展住宿以外的新领域
  • 俄罗斯期望乌克兰在停火期间采取行动缓和局势
  • 南京大屠杀幸存者刘贵祥去世,享年95岁
  • 徐丹任武汉大学药学院院长:研究领域在国际上处领跑地位
  • 特朗普称加总理将很快访美,白宫:不影响将加拿大打造成“第51个州”计划
  • 美乌矿产协议签署被曝“临门一脚”时生变,美方提附加条件