数据结构之平衡二叉树
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;
}