构造AVL树基础 + 力扣1382. 将二叉搜索树变平衡
构造AVL树基础#
定义#
对于任意一个节点,左子树和右子树的高度差不能超过1。
怎么计算#
- 标注节点的高度
- 计算平衡因子
如何维持平衡#
如果平衡被打破需要根据不同的情况来旋转修改树,如插入2后,原有的平衡被打破,节点8变得不平衡:
左旋操作#
针对情况 RR
插入的元素在不平衡的节点的右侧的右侧,即 RR
对节点y进行向左旋转操作,返回旋转后的新的根节点x
y x
/ \ / \
T4 x 向左旋转 (y) y z
/ \ --------------> / \ / \
T3 z T4 T3 T1 T2
/ \
T1 T2
具体步骤
- x.left = y
- y.right = T3
- 更新节点高度
初始条件T4 < y < T3 < x < T1 < z < T2
y x
/ \ / \
T4 x 向左旋转 (y) y z
/ \ --------------> / \ / \
T3 z T4 T3 T1 T2
/ \
T1 T2
step 1: x.left = y
y x
/ \ / \
T4 x y z T3(单独)
/ \ --------------> / / \
T3 z T4 T1 T2
/ \
T1 T2
step 2: y.right = T3
x
/ \
y z
/ \ / \
T4 T3 T1 T2
右旋操作#
针对情况 LL
插入的元素在不平衡的节点的左侧的左侧,即 LL
对节点y进行向右旋转操作,返回旋转后的新的根节点x
y x
/ \ / \
x T4 向右旋转 (y) z y
/ \ --------------> / \ / \
z T3 T1 T2 T3 T4
/ \
T1 T2
具体步骤
- x.left = y
- y.right = T3
- 更新节点高度
初始条件T1 < z < T2 < x < T3 < y < T4
y x
/ \ / \
x T4 向右旋转 (y) z y
/ \ --------------> / \ / \
z T3 T1 T2 T3 T4
/ \
T1 T2
step 1: x.right = y
y x
/ \ / \
x T4 z y T3(单独)
/ \ --------------> / \ \
z T3 T1 T2 T4
/ \
T1 T2
step 2: y.left = T3
x
/ \
z y
/ \ / \
T1 T2 T3 T4
LR和RL#
LR
首先对x进行左旋转,转化为了了LL的情况,接下来按LL处理
RL:
y y
/ \ / \
x T4 首先对x进行左旋转 z T4
/ \ --------------> / \
T1 z x T3
/ \ / \
T2 T3 T1 T2
RL
首先对x进行右旋转,转化为了了RR的情况,接下来按RR处理
LR:
y y
/ \ / \
T1 x 首先对x进行右旋转 T1 z
/ \ --------------> / \
z T4 T2 x
/ \ / \
T2 T3 T3 T4
给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。如果有多种构造方法,请你返回任意一种。
如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1
,我们就称这棵二叉搜索树是 平衡的 。
示例 1:
输入:root = [1,null,2,null,3,null,4,null,null]
输出:[2,1,3,null,null,null,4]
解释:这不是唯一的正确答案,[3,1,4,null,2,null,null] 也是一个可行的构造方案。
示例 2:
输入: root = [2,1,3]
输出: [2,1,3]
提示:
- 树节点的数目在
[1, 104]
范围内。 1 <= Node.val <= 105
两个题解都来自burning-summer
都是java写的,本文用的c++
题解一:最优并且不需要完整构建AVL树#
手撕AVL并不是最优解,只是通解,时间复杂度是nlog(n)。
利用二叉搜索树的性质,中序遍历输出,然后以中间为root,递归构造树,效率更高,算是本题的最优解。
查看代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> vec;
//按中序遍历并依次存储节点
void inOrder(TreeNode* cur){
if(cur==nullptr)
return;
inOrder(cur->left);
vec.emplace_back(cur->val);
inOrder(cur->right);
}
TreeNode* buildTree(int l,int r){
if(l>r)
return nullptr;
int mid=floor((l+r)/2);
//中间节点作为根节点,把中间节点的左右节点作为根节点的叶子节点
// int mid = start + (end - start >> 1);
TreeNode* root =new TreeNode(vec[mid]);
// 递归构造左右子树
root->left = buildTree(l,mid-1);
root->right = buildTree(mid+1,r);
// 返回根节点
return root;
}
TreeNode* balanceBST(TreeNode* root) {
inOrder(root);
return buildTree(0,vec.size()-1);
}
};
题解二:构造AVL树#
先遍历得到原树的节点,然后依次添加到AVL树中。
查看代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//记录node节点的高度
map<TreeNode*,int> height;
TreeNode* balanceBST(TreeNode* root) {
if(root==nullptr)
return nullptr;
//通过插入操作建树
stack<TreeNode*> st;
TreeNode* newroot=nullptr;
TreeNode* node=root;
// 先序遍历插入(其实用哪个遍历都行)
while(node!=nullptr||!st.empty()){
if(node!=nullptr){
// 不停插入新节点,得到当前的根节点
newroot=insert(newroot,node->val);
st.push(node);
node=node->left;//先序一直往左走
}
else{//左边为空开始取右边的
node=st.top();
st.pop();
node=node->right;
}
}
return newroot;
}
// 新节点插入
TreeNode* insert(TreeNode* root,int val){
if(root==nullptr){
root=new TreeNode(val);
height.insert({root,1});//新节点的高度为1
return root;
}
TreeNode* node=root;
int cmp=val-node->val;
if(cmp<0){
//左子树插入
node->left=insert(root->left,val);
//如果左右子树高度差超过1,进行旋转调整
if(height[node->left]-height[node->right]>1){
if(val>node->left->val){
//插入在右孩子左边,RL,右孩子先右旋,转换为RR
node->left = rotateLeft(node->left);
}
//节点左旋,处理RR情况
node=rotateRight(node);
}
}else if(cmp>0){
// 右子树插入
node->right = insert(root->right,val);
// 如果左右子树高度差超过1,进行旋转调整
if (height[node->right] - height[node->left] > 1){
if (val < node->right->val){
// 插入在右孩子左边,右孩子先右旋
node->right = rotateRight(node->right);
}
// 节点左旋
node = rotateLeft(node);
}
}
else{
// 一样的节点,啥都没发生
return node;
}
// 获取当前节点新高度
int curheight = getCurNodeNewHeight(node);
// 更新当前节点高度
height[node]=curheight;
return node;
}
/**
* node节点左旋
对节点y进行向左旋转操作,返回旋转后的新的根节点x
y x
/ \ / \
T4 x 向左旋转 (y) y z
/ \ --------------> / \ / \
T3 z T4 T3 T1 T2
/ \
T1 T2
*/
TreeNode* rotateLeft(TreeNode* node){
// ---指针调整
TreeNode* right = node->right;
node->right = right->left;
right->left = node;
// ---高度更新
// 先更新node节点的高度,这个时候node是right节点的左孩子
int newNodeHeight = getCurNodeNewHeight(node);
// 更新node节点高度
// nodeHeight.put(node,newNodeHeight);
height[node]=newNodeHeight;
// newNodeHeight是现在right节点左子树高度,原理一样,取现在right左右子树最大高度+1
int newRightHeight = max(newNodeHeight,height[right->right]) + 1;
// 更新原right节点高度
// nodeHeight.put(right,newRightHeight);
height[right]=newRightHeight;
return right;
}
/**
* node节点右旋
对节点y进行向右旋转操作,返回旋转后的新的根节点x
y x
/ \ / \
x T4 向右旋转 (y) z y
/ \ --------------> / \ / \
z T3 T1 T2 T3 T4
/ \
T1 T2
*/
TreeNode* rotateRight(TreeNode* node){
// ---指针调整
TreeNode* left = node->left;
node->left = left->right;
left->right = node;
// ---高度更新
// 先更新node节点的高度,这个时候node是right节点的左孩子
int newNodeHeight = getCurNodeNewHeight(node);
// 更新node节点高度
// nodeHeight.put(node,newNodeHeight);
height[node]=newNodeHeight;
// newNodeHeight是现在left节点右子树高度,原理一样,取现在right左右子树最大高度+1
int newLeftHeight = max(newNodeHeight,height[left->left]) + 1;
// 更新原left节点高度
// nodeHeight.put(left,newLeftHeight);
height[left]=newLeftHeight;
return left;
}
/**
* 获取当前节点的新高度
* @param node node
* @return 当前node的新高度
*/
int getCurNodeNewHeight(TreeNode* node){
// node节点的高度,为现在node左右子树最大高度+1
return max(height[node->left],height[node->right]) + 1;
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2019-02-10 UVA 1592 DataBase
2019-02-10 UVA 400 Unix ls