小老弟们!我今天继续更新一些内容,是关于平衡二叉树的。其它博客写的都挺好,就是我看不懂,前几天纠结了好一会终于弄懂了,马上来总结下。
针对AVL先抛出三个问题"What-Why-How"?
AVL树是什么?AVL是苏联数学家和以色列计算科学家的Adelson-Velsky和Landis两个人合作为了解决二叉排序树增删节点导致左右子树高度不平衡的问题提出的一种新型二叉树结构,又叫Height Balanced Binary Search Tree或Self-Balanced Binary Search Tree。
为什么使用AVL树?在AVL树被提出之前,用来加快数据访问的数据结构分别有散列表和二叉排序树,这两个结构都具有快速访问数据的能力。
散列表是通过散列函数计算hash值然后跟数组容量做模运算,得出来的结果就是在数组的对应的下标,但是如果数据量足够大的情况下,十分容易发生hash碰撞,对应的解决方法有:1、开放地址法,如果hash碰撞则继续hash直到不碰撞为止。2、链地址法,hash碰撞直接按照链表的方式链接起来,查找时先定位下标然后遍历遍历比较、找到值为止。第一种方法当哈希表越来越满时聚集越来越严重,这导致产生非常长的探测长度,后续的数据插入将会非常费时。通常数据超过三分之二满时性能下降严重,因此设计哈希表关键确保不会超过这个数据容量的一半,最多不超过三分之二。第二种方法更糟糕,当大量数据都发生碰撞时,最严重情况下会形成一条高度为n的链表,这样就只能遍历了,时间复杂度是O(n)表现也很糟糕。不过在小容量比如缓存等这些还是非常有用的,但不适用于大量数据的组织形式。
二叉排序树按照Key(lchild)=<Key(parent)=<Key(rchild)的形式,使用中旬遍历时形成的是有序列表。假设现在有一颗完美的满二叉树,包含了n个节点,根据二叉树的性质,我们直到n=2^h-1,h是树的高度,那么h=log2n。这就意味着二叉排序树相对于hash表的时间复杂度呈线性增长来说,BST是代数级增长,量越大优势越明显。但这也只是假设而已,极端情况下也会出现链式结构,问题是在于高度,如果出现链式,那么h=n,即不利于遍历也不利于文件IO。因此二叉排序树需要平衡才能发挥log2n的威力。
AVL树涉及到三个操作:1、根据值查询(废话)。2、插入新结点。3、删除结点。
在说怎么做之前,还得先了解什么叫递归和平衡因子,其实二叉排序树没什么难的,就只需要了解递归和平衡因子足够了。高中的归纳方法总该不会忘吧,在通俗易懂点就是套娃,但递归是由终止条件。把下一个递归调用返回的值用做本次的结果来用,像各类语言自己实现的斐波那契数列一样,那就是递归调用的威力,写一下伪代码的实现吧,如下:
def fibonacci(n):
if !n:
return 0
return n+fibonacci(n-1)
然后关于平衡因子,你们需要记住两个公式就够了。AVL树要求父节点(parent)的左右子树高度差绝对值不超过1,由此得第一个公式:Height(parent)=Height(parent->lchild) - Height(parent->rchild)。我们要知道节点是否平衡,用上面的式子计算出父节点的高度即可。但里面有一种未知变量,那就是子树的高度Height(child),由此再引出第二个式子:Height(child)=max(Height(child.lchild),Height(child.rchild))+1。注意到了吗,这里求子树高度就用到了递归!!在子树高度中我们只选取高度最高的一棵树,+1是因为到父节点高度就是1。
树高的调整也是递归处理,插入和删除的操作都差不多,都是插入/删除、更新平衡因子、调整树。不过插入只是创建新节点直接跟在叶子节点后面,而删除是区分不同情况的,情况包括:删除结点是叶子节点;删除结点只有一个叶子节点;删除结点有两个叶子节点!!
哈哈,大概说了一下。直接上代码!!!迎接暴风吧!!!
package main
import (
"fmt"
)
// AVL-平衡二叉树的实现
// 1、搜索
// 2、插入---- 按照顺序插入、更新平衡因子、根据平衡因子大小调整最小失衡二叉树
// 3、删除---- 实际操作和插入的步骤基本一致,不过插入节点是直接把新增节点当作左或右子树,不需要考虑指向问题。而删除需要考虑三种情况:如果删除节点是叶子节点,那就直接删除;
// 如果删除节点有一个左或右孩子,那么把孩子的值替换到节点,然后指向孩子的指针置为空;如果删除节点有两个孩子,那么
// 总的来说,删除操作的内容借鉴了二叉排序树的删除处理
// AVL最重要的就是平衡因子和调整的旋转,平衡因子的计算总结:1、平衡因子等于左子树高度-右子树高度,2、递归计算子树的高度在其左和右子树高度中取最大值+1,
// ①BF(P)=Height(P->lchild) - Height(P->rchild)
// ②Height(P->lchild)=max(Height(P->lchild->lchild), Height(P->lchild->rchild)) + 1
// 二叉树结构定义
type AVLNode struct {
Left, Right *AVLNode // 表示指向左孩子和右孩子
Data int // 结点存储数据
Height int // 每个节点的平衡因子,若该节点的平衡因子绝对值大于1则不平衡,需要调整这个子树结构
}
// 搜索
func (avlNode *AVLNode) Get(key int) *AVLNode {
q := avlNode
if avlNode != nil {
if key > avlNode.Data {
// 大于当前节点值,往右走
q = avlNode.Right.Get(key)
} else if key < avlNode.Data {
// 小于当前节点值,往左走
q = avlNode.Left.Get(key)
} else if key == avlNode.Data {
q = avlNode
} else {
q = nil
}
}
return q
}
// 插入
// 实现步骤:
// 1、使用二叉搜索树的插入逻辑把新元素插进去
// 2、插入后,检查每个节点的平衡因子
// 3、如果每个节点的平衡因子为0或1或-1,则进行下一个操作
// 4、如果任何节点的平衡因子绝对值超过1,则称该树不平衡。在这种情况下,进行适当的旋转,使其平衡并进行下一步操作。
// 总的来说就是,插入,遍历节点查看平衡因子,如果平衡因子绝对值不超过2,则继续插入;如果超过,则执行平衡操作
func (avlNode *AVLNode) Insert(Value int) *AVLNode {
// 加锁
// treeNode.lock.Lock()
// defer treeNode.lock.Unlock()
// 创建叶子节点
if avlNode == nil {
return &AVLNode{
Left: nil,
Right: nil,
Height: