right: nil,
data: data,
}
}
复制代码
可以在二叉查找树的基础上进行修改,这里重点实现插入和删除的操作,其他一些基本上都和二叉树操作一样。
type AVLTree struct {
root *TreeNode
}
// Insert 插入
func (a *AVLTree) Insert(data int) {
// TODO
}
// Delete 删除
func (a *AVLTree) Delete(data int) {
// TODO
}
复制代码
下面还需要增加一些辅助函数,帮助后面插入和删除的操作。
3.2. 结点高度
在结点中包含了height
的属性,如果结点是nil
的时候返回0,否则返回结点中height
高度。
// height 获取结点的高度
func (a *AVLTree) height(node *TreeNode) int {
if node == nil {
return -1
}
return node.height
}
复制代码
3.3. 计算平衡因子
平衡因子:某个结点的左子树的高度减去右子树的高度得到的差值。
// balanceFactor 获取结点的平衡因子
func (a *AVLTree) balanceFactor(node *TreeNode) int {
if node == nil {
return 0
}
return a.height(node.left) - a.height(node.right)
}
复制代码
3.4. 判断当前二叉树是否为平衡二叉树
这个方法,后面结点调整会用到; 当插入数据之后,判断当前二叉树是否为平衡二叉树,不是的话需要调整。
// IsBalanceTree 判断是是否为平衡二叉树
func (a *AVLTree) IsBalanceTree() bool {
return a.isBalanceTree(a.root)
}
// isBalanceTree 递归判断
func (a AVLTree) isBalanceTree(node *TreeNode) bool {
// node是nil的,返回 true
if node == nil {
return true
}
// 获取当前结点的平衡因子
factor := a.balanceFactor(node)
// 如果平衡因子大于1的话,说明是非平衡二叉树
if factor * factor > 1 {
return false
}
// 判断左右子树平衡因子大于1
return a.isBalanceTree(node.left) && a.isBalanceTree(node.right)
}
复制代码
3.5. 结点增加
这里先使用上一节二叉查找树的中序遍历,进行数据展示。接着用递归实现二叉树查找树的插入:
// Insert 插入数据
func (a *AVLTree) Insert(data int) {
a.root = a.insert(a.root, data)
}
// max x,y两个树之间的最大值
func max(x, y int) int {
if x > y {
return x
}
return y
}
// insert 内置的递归函数构建二叉查找树
func (a *AVLTree) insert(node *TreeNode, data int) *TreeNode {
// 当前结点如果是nil,创建新结点并返回
if node == nil {
return NewTreeNode(data)
}
// 如果当前结点的data小于data值
if node.data < data {
// 从右子树添加
node.right = a.insert(node.right, data)
}
// 如果当前结点的data大于data值
if node.data > data {
// 从左子树添加
node.left = a.insert(node.left, data)
}
// 更新高度值
node.height = 1 + max(a.height(node.left), a.height(node.right))
// 计算当前结点的平衡因子
balanceFactor := a.balanceFactor(node)
fmt.Printf(“结点:%d:%d => %d\n”, node.data, node.height, balanceFactor)
return node
}
复制代码
3.6. 测试
这里通过生成1000个随机数,输出构建的二叉查树。
func TestNewTreeNode(t *testing.T) {
rand.Seed(time.Now().UnixNano())
tree := AVLTree{}
for i := 0; i < 1000; i++ {
tree.Insert(rand.Intn(1000))
}
tree.InOrderTraverse()
}
复制代码
四. 插入维护平衡
当有一个结点插入之后,会出现二叉树平衡性被打破,此时就需要去维护二叉树查找树,使二叉查找树保存平衡因子不超过1。
在3.5
中在构建二叉查找树过程中只设置height
和计算平衡因子。但是并没有维护插入结点之后对于整棵树的平衡性。
// insert 内置的递归函数构建二叉查找树
func (a *AVLTree) insert(node *TreeNode, data int) *TreeNode {
// 当前结点如果是nil,创建新结点并返回
if node == nil {
return NewTreeNode(data)
}
// 如果当前结点的data小于data值
if node.data < data {
// 从右子树添加
node.right = a.insert(node.right, data)
}
// 如果当前结点的data大于data值
if node.data > data {
// 从左子树添加
node.left = a.insert(node.left, data)
}
// 更新高度值
node.height = 1 + max(a.height(node.left), a.height(node.right))
// 计算当前结点的平衡因子
balanceFactor := a.balanceFactor(node)
// TODO 维护平衡
return node
}
复制代码
这里我们就需要讨论一下,当插入结点之后会出现不平衡的情况已经如何去维护平衡。
注意:在结点增加之前,二叉树查找树是平衡二叉树;在增加之后才会破坏原来的平衡状态。
4.1. RR型:右旋转
当增加结点的一直往左子树上增加,平衡因子超过1之后就会不平衡,如下图:
从图中可以看出,触发右旋转的条件:当前结点的平衡因子大于1并且当前结点的左子树的平衡因子大于等于0
,这样可以保证二叉树是向左侧偏的。接着看一下应该如何右旋转:
从上图可知:将B结点指向D2的指针指向A结点,再将A结点的左指针指向原来B结点指向的D2结点;最后还需要修改A、B两个结点的高度。
代码实现:
// rightSpin 右旋转
func (a *AVLTree) rightSpin(node *TreeNode) *TreeNode {
// 先保存指针指向的地址
lCh := node.left
lRCh := lCh.right
// 旋转
lCh.right = node
node.left = lRCh
// 更新height,先更新node结点(旋转之后node结点变成了,lCh的子结点),接着在更新lCh结点的高度
node.height = max(a.height(node.left), a.height(node.right)) + 1
lCh.height = max(a.height(lCh.left), a.height(lCh.right)) + 1
// 返回旋转之后的根结点
return lCh
}
复制代码
4.2. LL:左旋转
左旋转和右旋转是想反的操作。增加的结点一直向右侧偏移,平衡因子超过-1说明当前二叉树不是平衡二叉树。
从图中可以知道触发左旋转的条件是:当前结点的平衡因子小于-1并且当前结点的右子树的平衡因子小于等于0
,这样可以保证二叉树是向右侧偏的。接着看一下应该如何左旋转:
实现和右旋转是相反的,代码实现:
// leftSpin 左旋转
func (a *AVLTree) leftSpin(node *TreeNode) *TreeNode {
// 先保存指针指向的地址
rCh := node.right
rLCh := rCh.left
// 选装
rCh.left = node
node.right = rLCh
// 更新高度
node.height = max(a.height(node.left), a.height(node.right)) + 1
rCh.height = max(a.height(rCh.left), a.height(rCh.right)) + 1
// 返回旋转之后的根结点
return rCh
}
复制代码
4.3. LR:左旋转;右旋转
这种情况对应下图,指的是在某一个结点的左孩子的右子树上增加结点导致不平衡。
将失衡的状态,需要先进行左旋转转换为我们熟悉的RR型,然后按照RR型进行右旋转。
4.4. RL:右旋转;左旋转
这种情况对应下图,指的是在某一个结点的右孩子的左子树上增加结点导致不平衡。
将失衡的状态,需要先进行右旋转转换为我们熟悉的LL型,然后按照LL型进行左旋转。
4.5. 完善插入方法
将上面四种情况整合回溯的维护平衡的部分;这样就可以将二叉查找树转为AVL树。
先将维护平衡地方抽取成一个方法:
// maintain 维护平衡
func (a *AVLTree) maintain(node *TreeNode) *TreeNode {
// 计算当前结点的平衡因子
balanceFactor := a.balanceFactor(node)
// 右旋转
if balanceFactor > 1 && a.balanceFactor(node.left) >= 0 {
return a.rightSpin(node)
}
// 左旋转
if balanceFactor < -1 && a.balanceFactor(node.right) <= 0 {
return a.leftSpin(node)
}
// LR
if balanceFactor > 1 && a.balanceFactor(node.left) < 0 {
node.left = a.leftSpin(node.left)
return a.rightSpin(node)
}
// RL
if balanceFactor < -1 && a.balanceFactor(node.right) > 0 {
node.right = a.rightSpin(node.right)
return a.leftSpin(node)
}
return node
}
复制代码
插入方法的实现:
// Insert 插入数据
func (a *AVLTree) Insert(data int) {
a.root = a.insert(a.root, data)
}
// insert 递归插入数据
func (a *AVLTree) insert(node *TreeNode, data int) *TreeNode {
// 当前结点如果是nil,创建新结点并返回
if node == nil {
return NewTreeNode(data)
}
// 如果当前结点的data小于data值
if node.data < data {
// 从右子树添加
node.right = a.insert(node.right, data)
}
// 如果当前结点的data大于data值
if node.data > data {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/4798c890573b8d069f4627c434c1a6d0.jpeg)
Java高频面试专题合集解析:
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
更多Java架构进阶资料展示
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/4798c890573b8d069f4627c434c1a6d0.jpeg)
Java高频面试专题合集解析:
[外链图片转存中…(img-ItEipOkR-1713614014604)]
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
[外链图片转存中…(img-lDcQUXPl-1713614014604)]
更多Java架构进阶资料展示
[外链图片转存中…(img-qax8bBSe-1713614014605)]
[外链图片转存中…(img-53d3IVeW-1713614014605)]
[外链图片转存中…(img-LMiajsa9-1713614014605)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!