Day39:平衡二叉树
剑指Offer_编程题——平衡二叉树
题目描述:
输入一颗二叉树,判断该二叉树是否是平衡二叉树。在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树。
具体要求:
时间限制: C/C++ 1秒,其他语言2秒 空间限制: C/C++32M,其他语言64M
具体实现
1、背景知识介绍 在开始做本题之前,我们给大家详细介绍平衡二叉树的相关知识。在维基百科中是这样介绍平衡二叉树的:在计算机科学中,AVL树是最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logn)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。具体用动画来帮助大家理解平衡二叉树的概念:
AVL树的基本操作一般涉及运作同在不平衡的二叉查找树所运作的同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL旋转"。以下图表以四列表示四种情况,每行表示在该种情况下要进行的操作。在左左和右右的情况下,只需要进行一次旋转操作;在左右和右左的情况下,需要进行两次旋转操作。具体过程如下:
从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。具体动画如下:
可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。(这是与伸展树搜索相对立的,它会因为搜索而变更树结构。) 假设平衡因子是左子树的高度减去右子树的高度所得到的值,又假设由于在二叉排序树上插入节点而失去平衡的最小子树根节点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先节点),则失去平衡后进行的规律可归纳为下列四种情况: 1、单向右旋平衡处理LL:由于在a的左子树根节点的左子树上插入节点,a的平衡因 子由1增至2,致使以a为根的子树失去平衡,则需进行一次右旋转操作; 2、单向左旋平衡处理RR:由于在a的右子树根节点的右子树上插入节点,a的平衡因 子由-1变为-2,致使以a为根的子树失去平衡,则需进行一次左旋转操作; 3、双向旋转(先左后右)平衡处理LR:由于在a的左子树根节点的右子树上插入节 点,a的平衡因子由1增至2,致使以a为根的子树失去平衡,则需进行两次旋转 (先左旋后右旋)操作。 4、双向旋转(先右后左)平衡处理RL:由于在a的右子树根节点的左子树上插入节 点,a的平衡因子由-1变为-2,致使以a为根的子树失去平衡,则需进行 两次旋转(先右旋后左旋)操作。 在平衡的二叉排序树BBST (Balancing Binary Search Tree)上插入一个新的数据元素e的递归算法可描述如下: 1、若BBST为空树,则插入一个数据元素为e的新节点作为BBST的根节点,树的深度 增1; 2、若e的关键字和BBST的根节点的关键字相等,则不进行; 3、若e的关键字小于BBST的根节点的关键字,而且在BBST的左子树中不存在和e有相 同关键字的节点,则将e插入在BBST的左子树上,并且当插入之后的左子 树深度增加(+1)时,分别就下列不同情况处理之: ①BBST的根节点的平衡因子为-1(右子树的深度大于左子树的深度,则将根节点的平衡因子更改为0,BBST的深度不变; ②BBST的根节点的平衡因子为0(左、右子树的深度相等):则将根节点的平衡因子更改为1,BBST的深度增1; ③BBST的根节点的平衡因子为1(左子树的深度大于右子树的深度):则若BBST的左子树根节点的平衡因子为1:则需进行单向右旋平衡处理,并且在右旋处理之后,将根节点和其右子树根节点的平衡因子更改为0,树的深度不变; 4、若e的关键字大于BBST的根节点的关键字,而且在BBST的右子树中不存在和e有相同关键字的节点,则将e插入在BBST的右子树上,并且当插入之后的右子树深度增加(+1)时,分别就不同情况处理之。 以上就是平衡二叉树的基本内容,如果还有任何疑问,请去哔哩哔哩看这方面的相关视频平衡二叉树旋转详解。接下来我们解决该题。 2、实现思路 根据我们上面介绍关于平衡二叉树的相关知识可知:我们只需平衡二叉树就是左子树和右子树的高度差不能超过1,且左右子树必须是平衡二叉树;因此我们只需要从根节点开始,先判断左右子树的高度差是否超过1,然后接着判断左右子树是否是平衡二叉树。这里我们仍然用熟悉的递归思想。具体我们分别用java和Python将其实现。 1、首先我们用java实现:
public class Solution{
public boolean IsBalanced_Solution(TreeNode root){
if(root == null)
return true;
if(Math.abs(getDepth(root.left) - getDepth(root.right)) <= 1)
return (IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right));
else
return false;
}
public int getDepth(TreeNode root){
if(root == null)
return 0;
int left = getDepth(root.left);
int right = getDepth(root.right);
return (left > right ? left : right) + 1;
}
}
代码效果图如图所示:
正如我们之前的文章中提到,如果在牛客网中我们以上代码可以直接编译通过,因为它给我们定义了节点TreeNode,但如果在本地编译器(IDEA)中,我们得自己定义该节点,具体实现如下:
public class TreeNode{
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val){
this.val = val;
}
}
2、接下来我们用python实现
class Solution:
def IsBalanced_Solution(self, pRoot):
if pRoot is None:
return True
left = self.get_depth(pRoot.left)
right = self.get_depth(pRoot.right)
if abs(left - right) < 2:
return self.IsBalanced_Solution(pRoot.left) and self.IsBalanced_Solution(pRoot.right)
else:
return False
def get_depth(self, pRoot):
if pRoot is None:
return 0
left = self.get_depth(pRoot.left)
right = self.get_depth(pRoot.right)
return max(left, right) + 1
代码效果图如图所示:
用python实现TreeNode结构:
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
上面的方法就是利用递归思想来判断一棵树是不是平衡二叉树的,但是,有一个缺陷,就是每判断一棵树就要遍历一遍树种的节点;来计算树的高度,这样下来,整个判断过程,每个节点不止被遍历过一次了。我们可以想想有没有其他办法可以让所有节点可以只遍历一次。例如,一棵树只要知道他左右子树的深度以及左右子树是不是平衡二叉树就可以判定这棵树是不是平衡二叉树了。如果上面这种方法是从根节点遍历到叶子节点,那如果我们从叶子节点开始遍历,一层一层往上遍历,那不就实现了每个节点只遍历一次了吗?即:我们要先遍历左右子树,再左右子树组成的这颗树是不是平衡二叉树。其实这个过程就是我们二叉树的后序遍历。需要注意的是:我们需要设置一个全局变量boolean进行标记。具体我们也别用java和python将其实现。 1、首先我们用java实现:
public class Solution{
private boolean isBalanced = true;
public boolean IsBalanced_Solution(TreeNode root){
getDepth(root);
return isBalanced;
}
public int getDepth(TreeNode root){
if(!isBalanced || root == null)
return 0;
int left = getDepth(root.left);
int right = getDepth(root.right);
int depth = (left > right ? left : right) + 1;
if(Math.abs(left - right) < 2)
isBalanced = true;
else
isBalanced = false;
return depth;
}
}
代码效果图如图所示:
2、接下来我们用python将其实现
class Solution:
res = True
def IsBalanced_Solution(self, pRoot):
if pRoot is None:
return True
self.get_depth(pRoot)
return self.res
def get_depth(self, pRoot):
if pRoot is None:
return 0
if not self.res:
return 0
left = 1 + self.get_depth(pRoot.left)
right = 1 + self.get_depth(pRoot.right)
if abs(left - right) > 1:
self.res = False
return max(left, right)
代码效果图如图所示:
总结
本道题主要考察平衡二叉树,本题直截了当,思路清晰。很显然考察的是平衡二叉树的性质,我们之前的很多二叉树的题目中,均用到了递归,因此,本题在做题之前详细给大家介绍了平衡二叉树,并且在解题之前用动画的形式详细的为大家介绍了平衡二叉树的删除以及旋转。在做题时,我们分被用python和java将其实现。并且还在改进的过程中,用到了二叉树的后序遍历。因此,我们在做题的时候,应该多次尝试各种方法,扩展自己的思维,写出优质的代码。总之,我们要继续加油,争取早日找到工作,Good Luck!!!
参考文献
[1] Z_Y_D_ [2] 云原生手记 [3] 平衡二叉树
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- yum install空间不足
- prometheus-nginxlog-exporter构建Nginx日志监控
- s3cmd put文件的过程
- Ceph RGW配置Nginx代理出现S3Error: 403 (Forbidden)
- OmniDiskSweeper清理系统文件
- 【Kubernetes】通过ConfigMap修改容器的DNS
- 【Tensorflow 2.x】检验MKL
- kubernetes dashboard insecure配置
- seqtk抽取reads
- Version of Delve is too old for this version of Go【Goland Debug】报错
- python之turtle模块-黄金螺线
- python之turtle模块-生化危机
- Python之turtle模块-饼状图
- python之turtle模块-弧线
- Python之turtle模块-画圈圈