数据结构之平衡二叉树

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值