引言
在前面的系列文章中,我们讲过了二叉查找树的相关知识和实现(二叉树(一)–概述(附二叉搜索树实现))。在文章的最后,我们总结了二叉查找树的查找时间复杂度:
对于一个节点分布相对均衡的二叉查找树来说,如果节点总数是n,那么搜索节点的时间复杂度是O(logn),和树的深度是一样的。
但对于极端情况(每次插入数据大小都是增大的,或者减小的),外形上看就只有一半的树,查找时间复杂度就会退化成O(n)的
为了解决二叉查找树查找时间复杂度退化的问题,需要二叉数据的自平衡,也就是本节要讲的平衡二叉树
。通过自身的平衡调整,查找、插入、删除操作在平均和最坏的情况下都是O(logn)。二叉查找树的树高度影响了查找的效率,需要尽量减小树的高度,AVL树正是这样的树。
AVL树简介
平衡二叉树也叫做AVL树,AVL树的名字来源于它的发明作者G.M. Adelson-Velsky 和 E.M. Landis。AVL树是最先发明的自平衡二叉查找树(Self-Balancing Binary Search Tree,简称平衡二叉树。
平衡二叉树定义(AVL):
它或者是一颗空树,或者具有以下性质的二叉排序树:它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。
一棵AVL树有如下必要条件:
- 它必须是二叉查找树(具体定义参照二叉搜索树)
- 任意一个节点的左子树和右子树的高度差至多为1。
图一中左边二叉树的节点45的左孩子46比45大,不满足二叉搜索树的条件,因此它也不是一棵平衡二叉树。
右边二叉树满足二叉搜索树的条件,同时它满足条件二,因此它是一棵平衡二叉树。
左边二叉树的节点45左子树高度2,右子树高度0,左右子树高度差为2-0=2,不满足条件二;
右边二叉树的节点均满足左右子树高度差至多为1,同时它满足二叉搜索树的要求,因此它是一棵平衡二叉树。
AVL树的查找、插入、删除操作在平均和最坏的情况下都是O(logn),这得益于它时刻维护着二叉树的平衡。
如果我们需要查找的集合本身没有顺序,在频繁查找的同时也经常的插入和删除,AVL树是不错的选择。
不平衡的二叉查找树在查找时的效率是很低的,因此,AVL如何维护二叉树的平衡是我们的学习重点。
AVL树相关概念
-
平衡因子
:将二叉树上节点的左子树高度减去右子树高度的值称为该节点的平衡因子BF(Balance Factor)。
在图二右边的AVL树上:
节点50的左子树高度为3,右子树高度为2,BF= 3-2 = 1;
节点45的左子树高度为2,右子树高度为1,BF= 2-1 = 1;
节点46的左子树高度为0,右子树高度为0,BF= 0-0 = 0;
节点65的左子树高度为0,右子树高度为1,BF= 0-1 = -1;
对于平衡二叉树,BF的取值范围为[-1,1]。如果发现某个节点的BF值不在此范围,则需要对树进行调整。 -
最小不平衡子树
:距离插入节点最近的,且平衡因子的绝对值大于1的节点为根的子树.。
在图三中,左边二叉树的节点45的BF = 1,插入节点43后,节点45的BF = 2。节点45是距离插入点43最近的BF不在[-1,1]范围内的节点,因此以节点45为根的子树为最小不平衡子树。
AVL树的平衡调整
每次添加和删除元素时,若出现不平衡(根据平衡因子进行判断),则要根据新插入的结点与最低不平衡结点的位置关系进行一次或多次旋转来调整树的平衡。
分为LL型、RR型、LR型和RL型4种类型,各调整方法如下(下面用A表示最低不平衡结点):
LL型调整
由于在A的左孩子(L)的左子树(L)上插入新结点,使原来平衡二叉树变得不平衡,此时A的平衡因子由1增至2。下面图1是LL型的最简单形式。显然,按照大小关系,结点B应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡,A结点就好像是绕结点B顺时针旋转一样。
LL型调整的一般形式如下图2所示,表示在A的左孩子B的左子树BL(不一定为空)中插入结点(图中阴影部分所示)而导致不平衡( h 表示子树的深度)。这种情况调整如下:①将A的左孩子B提升为新的根结点;②将原来的根结点A降为B的右孩子;③各子树按大小关系连接(BL和AR不变,BR调整为A的左子树)。
代码实现:
RR型调整
由于在A的右孩子®的右子树®上插入新结点,使原来平衡二叉树变得不平衡,此时A的平衡因子由-1变为-2。图3是RR型的最简单形式。显然,按照大小关系,结点B应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡,A结点就好像是绕结点B逆时针旋转一样。
RR型调整的一般形式如下图4所示,表示在A的右孩子B的右子树BR(不一定为空)中插入结点(图中阴影部分所示)而导致不平衡( h 表示子树的深度)。这种情况调整如下:
- 将A的右孩子B提升为新的根结点;
- 将原来的根结点A降为B的左孩子
- 各子树按大小关系连接(AL和BR不变,BL调整为A的右子树)。
代码实现:
LR型调整
由于在A的左孩子(L)的右子树®上插入新结点,使原来平衡二叉树变得不平衡,此时A的平衡因子由1变为2。图5是LR型的最简单形式。显然,按照大小关系,结点C应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡。
LR型调整的一般形式如下图6所示,表示在A的左孩子B的右子树(根结点为C,不一定为空)中插入结点(图中两个阴影部分之一)而导致不平衡( h 表示子树的深度)。这种情况调整如下:①将B的左孩子C提升为新的根结点;②将原来的根结点A降为C的右孩子;③各子树按大小关系连接(BL和AR不变,CL和CR分别调整为B的右子树和A的左子树)。
代码实现:
RL型调整
由于在A的右孩子®的左子树(L)上插入新结点,使原来平衡二叉树变得不平衡,此时A的平衡因子由-1变为-2。图7是RL型的最简单形式。显然,按照大小关系,结点C应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡。
RL型调整的一般形式如下图所示,表示在A的右孩子B的左子树(根结点为C,不一定为空)中插入结点(图中两个阴影部分之一)而导致不平衡( h 表示子树的深度)。这种情况调整如下:①将B的左孩子C提升为新的根结点;②将原来的根结点A降为C的左孩子;③各子树按大小关系连接(AL和BR不变,CL和CR分别调整为A的右子树和B的左子树)。
代码实现:
代码实现
package main
/*
AVL树
*/
import "fmt"
// 平衡二叉树
type AVLTree struct {
RootNode *TreeNode //树的根节点
}
//平衡二叉树节点
type TreeNode struct {
Data int32 // 节点上的数据
Left *TreeNode // 指针指向左孩子节点
Right *TreeNode // 指针指向右孩子节点
Height int // 该节点高度,方便计算平衡因子(相比二叉查找树新增的节点信息,用来判断是否需要进行自平衡调整)
}
// 判断平衡二叉树是否为空树,只需要判断第一个元素是否是 nil
func (self *AVLTree) IsEmpty() bool {
if self.RootNode == nil {
return true
} else {
return false
}
}
// look LL
func left_left_rotation(k *TreeNode) *TreeNode {
var kl *TreeNode
kl = k.Left
k.Left = kl.Right
kl.Right = k
k.Height = max(k.Left.height(), k.Right.height()) + 1
kl.Height = max(kl.Left.height(), k.height()) + 1
return kl
}
//look RR
func right_right_rotation(k *TreeNode) *TreeNode {
var kr *TreeNode
kr = k.Right
k.Right = kr.Left
kr.Left = k
k.Height = max(k.Left.height(), k.Right.height()) + 1
kr.Height = max(k.height(), kr.Right.height()) + 1
return kr
}
//look LR
func left_righ_rotation(k *TreeNode) *TreeNode {
k.Left = right_right_rotation(k.Left)
return left_left_rotation(k)
}
//look RL
func right_left_rotation(k *TreeNode) *TreeNode {
k.Right = left_left_rotation(k.Right)
return right_right_rotation(k)
}
// 取最大值方法
func max(a ...int) int {
if len(a) < 1 {
panic("params count < 1")
}
max := a[0]
for _, i := range a {
if i > max {
max = i
}
}
return max
}
func (node *TreeNode) Add(value int32) *TreeNode {
/*
在平衡二叉树里添加数据
和二叉查找树的插入方法有所不同
二叉查找树Add方法的拥有者是树,平衡二叉树的Add方法拥有者是头节点
因为平衡二叉树在插入数据时需要更新其父代节点的高度,所以要使用递归
*/
if node == nil {
node = &TreeNode{
Data: value}
} else if value < node.Data {
node.Left = node.Left.Add(value)
if node.Left.height()-node.Right.height() == 2 {
if value < node.Left.Data {
//LL
node = left_left_rotation(node)
} else {
// LR
node = left_righ_rotation(node)
}
}
} else if value > node.Data {
node.Right = node.Right.Add(value)
if (node.Right.height() - node.Left.height()) == 2 {
if value < node.Right.Data {
// RL
node = right_left_rotation(node)
} else {
//RR
node = right_right_rotation(node)
}
}
} else if value == node.Data {
fmt.Println("the value", value, "has existed!")
}
//注意:更新高度(可能是不插入旋转值,因此您应该更新高度)
node.Height = max(node.Left.height(), node.Right.height()) + 1
return node
}
//删除指定index节点
//查找节点 ---> 删除节点 ----> 调整树结构
//删除节点时既要遵循二叉搜索树的定义又要符合二叉平衡树的要求 ---> 重点处理删除节点的拥有左右子树的情况
func (node *TreeNode) Delete(value int32) *TreeNode {
//todo
return nil
}
// 这个很关键,和更新节点高度息息相关
func (node *TreeNode) height() int {
if node == nil {
return -1
}
return node.Height
}
// 查找元素所在的节点
func (self *AVLTree) Get(value int32) *TreeNode {
node := self.RootNode
for node != nil {
if value < node.Data {
node = node.Left
} else if value > node.Data {
node = node.Right
} else {
return node
}
}
return nil
}
//前序遍历
func (self *TreeNode) preOrderTraversal(res *[]int32) []int32 {
if self == nil {
return *res
}
*res = append(*res, self.Data)
self.Left.preOrderTraversal(res)
self.Right.preOrderTraversal(res)
return *res
}
func NewAVLTree() *AVLTree {
return new(AVLTree)
}
func main() {
avlTree := NewAVLTree()
data := []int32{
9, 3, 2, 1, 4, 5, 6, 7, 16, 15, 14, 13, 12, 11, 10, 8}
for _, value := range data {
avlTree.RootNode = avlTree.RootNode.Add(value)
}
fmt.Println(avlTree.RootNode.Data)
fmt.Println(avlTree.Get(7).height())
var res []int32
fmt.Println(avlTree.RootNode.preOrderTraversal(&res))
}