数据结构之树从入门到如土(二)----带你从头碾压 一颗AVL树(多图)

AVL树 解决了二叉树的什么问题?

AVL 和 二叉树的 最大区别是能自平衡,简单的说就是多了一步插入的时候会按照插入的值做 自平衡 防止 插入 向 1 ,3 ,4, 6, 8, 9 这样 左边的值 比右边小 就会变成 链表了。
在这里插入图片描述

AVL树的定义:AVL树是二叉树,其各个节点的左右子树的高度相差不超过1。

AVL树是最早提出的自平衡二叉树,在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。AVL树得名于它的发明者G.M. Adelson-Velsky和E.M. Landis。

  1. AVL树查找、插入和删除在平均和最坏情况下都是O(log n),增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
  2. AVL 比较适合 不需要删除 值插入的场景。
平衡因子:

二叉树和AVL树不同体现在,AVL树的平衡因子不会超过1 而二叉树的平衡 因子就没有上限了,如果 平衡 因子 + 1 和 你插入的 数据长度一样 就变成链表了。 那什么是 平衡 因子呢,

划重点

平衡因子 : 左边子树的高度 - 右边子树的高度
那怎么定义 高度呢?
高度定义 = max{左子树.高度,右子树.高度} + 1
特殊情况: 如果没有左右子节点了 那么 返回 -1
在这里插入图片描述
有了 定义 我们就可以 来实现一个最简单的计算AVL树的 平衡因子的功能了,来一步步实现。
首先 我们看下 基本的数据结构 和树的定义一样,node节点要有左边和右边的树枝和 一个 存储Key的变量 ,然后我们再定义一个 一颗树的根节点的结构,要有根节点 和包含节点的数量。

type avltree struct {
	//定义根节点
	root *node
	//包含节点的数量
	size int
}
//定义一颗二叉树
func NewAvTree() *avltree{
	rt := NewAvlNode()
	return &avltree{
		root:rt,
	}
}
type node struct {
	Key int //值
	left *node //左子树
	right *node //右子树
	height int //当前树的高度 = max{left.height,right.height}
}
//新建avl树的节点
func NewAvlNode() *node{
	return &node{
		Key: 0,
		left:  nil,
		right: nil,
		height:0,
	}
}

二叉树的递归 之 查找

在这里插入图片描述

递归是一种计算过程,如果其中每一步都要用到前一步或前几步的结果,称为递归的。用递归过程定义的函数,称为递归函数,例如连加、连乘及阶乘等。凡是递归的函数,都是可计算的,即能行的 .
从上面图上可以看出,一颗树 从上至下 就只有左子树 和 右子树 只能往左 往右走
那么 往左往右.那么 我们就可以通过递归 实现 便利 整颗数的结构。

下面 我们 实现AVL树其中最简单的功能,同时 下面的增删 使用的的递归的方式也是大同小异的。

func (avlt *avltree) Find(key int) *node{
	return find(avlt.root,key)
}
func find(avlnode *node,key int) *node{
	if avlnode == nil{
		return nil
	}
	if avlnode.Key > key{
		ret := find(avlnode.left,key)
		return ret
	}else if avlnode.Key < key{
		ret :=find(avlnode.right,key)
		return ret
	}else{
		return avlnode
	}
}

上面 3 的 判断 分别是大于小于 和等于 只有等于才返回当前node的值,所以 如果没找到返回值是nil。

实现二叉树insert方法

有了 树 我们 还要将数据 加入到 树才行,下面 将要实现 Insert方法
插入 就是比大小,递归其实很简单。
在这里插入图片描述

//插入key
func (avlt *avltree) Insert(key int){
	//第一次插入 将key 插入root 
	if avlt.size == 0{
		avlt.root.Key = key
	}else{
		avlt.root = insert(avlt.root,key)
	}
	//二叉树节点数
	avlt.size ++
}

func insert(root *node,key int) *node{
	//递归的 结束条件 当叶子节点时 插入节点
	if root == nil{
		//在叶子节点下面建立新节点
		nd := NewAvlNode()
		nd.Key = key
		//把叶子结点 返回给上一个节点
		return nd
	}
	//往右边 遍历
	if key > root.Key{
		root.right = insert(root.right,key)
	//往左边 遍历
	}else if key < root.Key{
		root.left = insert(root.left,key)
	}
	//返回节点
	return root
}
func main(){
	a := NewAvTree()
	a.Insert(1)
	a.Insert(2)
	a.Insert(3)
	a.Insert(4)
	fmt.Println(a)
}

在这里插入图片描述
我们 插入 4个节点,此时 构造的就是一颗不平衡 的二叉树,节点只有往右一个方向.

二叉树计算 高度和平衡因子

好了准备工作完成,我们来实现前面 定义的 2个公式吧 分别计算高度 和 平衡因子.
上面 我们讲过 定义高度 的方法是 max{左子树.高度,右子树.高度} + 1, 假设我们是计算机 我们 可不可以直接知道 45 的高度呢,不行吧 如果你从第一个节点 看去 那么你是没法知道下面 有多少节点的.
在这里插入图片描述
我们 不能从上边直接获得节点的 高度 只能从叶子节点 自下而上 逐层返回 然后计算高度。
所以我们需要
那假设 这种情况下 我们是不是 可以知道 这些节点的 高度呢? 答案是肯定的 因为 他们已经是叶子结点了 没有子节点了,所以就能知道 他们的节点 高度为0 ,所以 我们需要递归到叶子节点 然后通过return 让他们 返回 字节的高度 告诉上一层,所以需要用到万能的递归。
在这里插入图片描述

func getHeight(root *node) int{
	//如果 是nil 节点 返回-1
	if root == nil{
		return -1
	}
	//左边节点的高度
	left := getHeight(root.left)
	//右边节点的高度
	right := getHeight(root.right)
	//计算 叶子结点返回给到 我的高度 然后通过 max{root.right,root.left} + 1 计算出本节点的高度
	root.height = Max(right,left) + 1
	//返回本节点的高度 告诉给上一层
	return root.height
}

在这里插入图片描述
是不是 很简单,有了 高度接下来 我们就可以计算平衡因子了。

平衡因子:通过左子树的高度 - 右子树的高度

那我们只需要在计算高度的时候加上,这条公式就好了。

func getHeight(root *node) int{
..........省略前面代码
//新增以下代码
	balancefractor := left - right
	fmt.Println("平衡因子:",balancefractor)
........
}

下面 我们测试下 看看:
在这里插入图片描述

func main(){
	a := NewAvTree()
	a.Insert(1)
	a.Insert(2)
	a.Insert(3)
	a.Insert(4)
	getHeight(a.root)
	fmt.Println(a)
}

在这里插入图片描述
可以看到 结果和我们图上画的是一样的,我们构造了一颗 极度不平衡的数,他的平衡 从上图中 我们可以得出 :当一棵树的 平衡因子的绝对值 == 树中包含节点的数量 -1 时 就退化成了链表。

同时 我们 可以得出 当 平衡因子 小于 -1 时需要左旋,当平衡 因子大于 1时 需要右旋
图中 树明显 往右倾严重,我们需要调整让部分节点分给左边,使他们平衡,这样称为左旋,右旋的话刚好相反。

什么时候 二叉树的平衡因子的绝对值 大于 1

有了判定二叉树的定义我们还需要知道什么时候二叉树失去了平衡,这样我们才知道该怎么调整。
首先 我们需要 理解的是,
一:插入一棵树 只能使其祖先节点失去平衡,他的父亲节点或者非祖先节点是不可能失去平衡的

在 AV树里面只需要判断 4中情况 ,分别是左旋 右旋,左右旋,和右左旋.
当我们 找到那么不平衡的节点时,我们只需要对下面的 子节点 和子节点的子节点进行操作就可以了。

在这里插入图片描述
在树失去平衡前它总是一颗平衡的数,那么 他左边的高度和右边的高度差的绝对值 相差不大于1,那么 我们抽象出 上图一样的三个节点,在插入前它是平衡的,当新插入一个节点是,他下面的子树 高度发生了变化 这样就会导致 上面的 非父节点 的平衡因子 都发生变化,这取决于 祖先节点 另一个子树 的高度, 就像 天平的 2端 如果质量相近就不会失去平衡。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
上面 是 二叉树 出现不平衡所有可能出现的情况,
总结上面图 所讲述的:
1. 不平衡 必然发生在祖先节点上.
2. 任何产生不平衡节点,我们只需要判断调整它下面的子节点,孙子节点就可以 使它继续保持平衡(其实 产生 不平衡 就是因为 你某一边 发育的 比 另一边好高度高 那么最简单的平衡方法 就是 让我下面平衡的节点 做我的 父节点 我做子节点这样 新插入的节点 和我就没什么关系了 我已经 不是 原来新插入节点的祖先了 那么 就保持了 插入前 的平衡了)
3. 我们要判断 到底需要哪种 旋转, 只需要判断 不平衡节点下面的 左边或右边 下面的左右节点的高度 谁的 高度 高 那么 肯定 是发生了插入的节点,如果是 树往左边倾斜的情况 我们有2 种处理 方式 如果 左边的左边 插入了节点 那么单次右旋(RR)就好了,如果左边的右边插入了节点 那么 先左旋后右旋(LRR),如果树往右边倾斜 则
相反.

树的左旋 和 右旋

一.右旋:

在这里插入图片描述

//右旋
func (avlnode *node) RightRotate() *node{
	tmp :=avlnode.left
	avlnode.left = tmp.right
	tmp.right = avlnode
	return avlnode
}
左旋

在这里插入图片描述

//左旋
func (avlnode *node) LeftRotate() *node{
	tmp :=avlnode.right
	avlnode.right = tmp.left
	tmp.left = avlnode
	return avlnode
}
左右旋转

在这里插入图片描述

//左右旋
func (avlnode *node) RLRoate() *node{
	avlnode.left = avlnode.left.LeftRotate()
	return avlnode.RightRotate()
}
右左旋转

在这里插入图片描述

//右左旋
func (avlnode *node) RLRoate() *node{
	avlnode.right = avlnode.right.RightRotate()
	return avlnode.LeftRotate()
}

下面 我们将 前面 getheight 稍微做些修改变成,自动掉整平衡因子,经过前面的分析 我们 知道 当节点出现不平衡时 需要 通过 调整和子节点之间的关系来,继续使当前不平衡的节点 保持平衡, 所以 下一个 难点 是如何判断 节点 该采取哪种旋转 方法.
前面 我们已经 提到过 那就是判断 左边或右边节点的左右节点的高度 的高度 左边的左边 节点高度 高 我们就用 一个右旋 如果 是左边的右边 或者 右边的左边 我们 就分别使用 左右 右左旋.

所以 核心 伪代码:
balance == left.height - right.height
if balace == 2{
}else if balace == -2 {
}
上面代码,就是计算 如果平衡因子使正的 说明 这棵树 往左倾斜了,否则 就是右倾.
if left.left > left.right {
node = node.leftrotate
}else{
node = node.LeftThenRightrotate()
}
上面 代码 主要判断节点 是 插入了左边的左边 还是 插入了左边的右边,谁高 那么就是插入了那一边,分别对应2种情况.左旋和 右左旋 ,所以 右边的 代码 同理 当 balance =等于-2 时 就去判断他的右节点的右节点的高度 和右节点的左节点的高度. 然后相应 的是 右旋 和 右左旋的情况.

//递归 传递node
func adjust(root *node) *node{
//如果为 nil返回
	if root == nil{
		return nil
	}
	//左边节点的高度
	root.left = getHeight(root.left)
	//右边节点的高度
	root.right = getHeight(root.right)

	var left int
	var right int
	//如果左边为 nil
	if root.left == nil{
	//高度为 -1
		left = -1
	}else{
	//否则高度为 左子节点返回的高度
		left = root.left.height
	}
	//如果右边为 nil 用来处理 返回为 nil的情况
	if root.right == nil{
	//高度为 -1
		right = -1
	}else{
	//否则高度为 右子节点返回的高度
		right =root.right.height
	}
	balance := left - right
	//左子树 的高度 比右子树的 高
	if balance == 2{
			//这里主要比较 哪里插入了新节点  判断nil 是因为 nil节点高度是获取不到的 只能这样判断
			if root.left.right == nil ||( root.left.left != nil  && root.left.left.height > root.left.right.height) {
				root = root.RightRotate()
			}else{
			//如果 左边的右边 发生了插入 先进行一次 左旋再 再进行一次右旋
				root = root.LRRoate()
			}

	}else if balance == -2{
	//这里主要比较 那里插入了新节点
		if root.right.left == nil || ( root.right.right != nil && root.right.right.height > root.right.left.height){
				root = root.LeftRotate()
			}else{
			//如果 右边的左边 发生了插入 先进行一次 右旋 再进行一次左旋
				root = root.RLRoate()
		}
	}
	//计算 叶子结点返回给到 我的高度 然后通过 max{root.right,root.left} + 1 计算出本节点的高度
	root.height = Max(left,right) + 1
	//返回本节点的高度 告诉给上一层
	return root
}

二叉树的删除

删除一个树的节点分为好几种情况,除了左右都不为nil 的情况其他的都很简单 直接返回 它的子节点。
在这里插入图片描述

如果删除的节点左右都有子节点 ,那 就需要找一个和被删除节点拥有一样性质的节点去替换 被删除节点。
在这里插入图片描述

所以 如果被删除的节点左边 和 右边都不为 nil 那么 ,我们 需要特殊处理
包括两步:

  1. 找到 被删除节点右边的最小节点。

首先我们 写一个 查找最小节点的方法,递归往左就能找到最小的节点.

//找到最小节点的上一个节点
func findMin(avlnode *node) *node{
	if avlnode.left == nil{
		return nil
	}
	min := findMin(avlnode.left)
	if min == nil{
		return avlnode
	}
	return avlnode
}

然后 我们实现下 删除功能:

func delelte(avlnode *node,key int) *node{
	if avlnode == nil{
		return nil
	}
	if  avlnode.Key > key{
		avlnode.left =delelte(avlnode.left,key)
	}else if avlnode.Key < key{
		avlnode.right = delelte(avlnode.right,key)
	}else{
		if avlnode.left != nil && avlnode.right != nil {
			//如果 右边节点已进是叶子节点 见如下图
			if avlnode.right.left == nil {
				//替换成 右边的节点的Key
				avlnode.Key = avlnode.right.Key
				//替换成 右边的节点的右子树
				avlnode.right = avlnode.right.right
				//上面2 步 后 当前节点就变成了右节点
			} else {
				//找到 最小节点的上面一个节点
				nodep := findMin(avlnode.right)
				//最小的一个节点
				minnode := nodep.left
				//处理左边最小节点 可能拥有的右节点
				nodep.left = nodep.left.right
				//替换 成最小一个节点的值
				avlnode.Key = minnode.Key
			}
			//一遍 为 nil 直接返回 另一边
		}else if avlnode.left == nil{
			avlnode = avlnode.right
		}else if avlnode.right == nil{
			avlnode = avlnode.left
		}
	}
	return avlnode
}

需要 注意的 是 我们只要 获取到 最小节点 的上一个节点这样我们就可以很方便的删除它了,并且 我们需要特殊处理一种情况。
在这里插入图片描述

为了测试 我们需要写一个中序遍历 在 进行插入后查看是否还是有序的输出,代码直接放在下面了。

在这里插入图片描述

全部代码:

package main

import "fmt"

type avltree struct {
	//定义根节点
	root *node
	//包含节点的数量
	size int
}
//定义一颗二叉树
func NewAvTree() *avltree{
	rt := NewAvlNode()
	return &avltree{
		root:rt,
	}
}
type node struct {
	Key int //值
	left *node //左子树
	right *node //右子树
	height int //当前树的高度 = max{left.height,right.height}
}

func NewAvlNode() *node{
	return &node{
		Key: 0,
		left:  nil,
		right: nil,
		height:0,
	}
}
//插入key
func (avlt *avltree) Insert(key int){
	//第一次插入 将key 插入root
	if avlt.size == 0{
		avlt.root.Key = key
	}else{
		avlt.root = insert(avlt.root,key)
	}
	avlt.size ++
	avlt.root = Adjust(avlt.root)
	//avlt.root = adjust(avlt.root)
}

func insert(root *node,key int) *node{
	//递归的 结束条件 当叶子节点时 插入节点
	if root == nil{
		//在叶子节点下面建立新节点
		nd := NewAvlNode()
		nd.Key = key
		//把叶子结点 返回给上一个节点
		return nd
	}
	//往右边 遍历
	if key > root.Key{
		root.right = insert(root.right,key)
	//往左边 遍历
	}else if key < root.Key{
		root.left = insert(root.left,key)
	}
	//返回节点
	return root
}

func Adjust(root *node) *node{
	if root == nil{
		return nil
	}
	//左边节点的高度
	root.left = Adjust(root.left)
	//右边节点的高度
	root.right = Adjust(root.right)

	var left int
	var right int
	if root.left == nil{
		left = -1
	}else{
		left = root.left.height
	}
	if root.right == nil{
		right = -1
	}else{
		right =root.right.height
	}
	balance := left - right
	//左子树 的高度 比右子树的 高
	if balance == 2{
			if root.left.right == nil ||( root.left.left != nil  && root.left.left.height > root.left.right.height) {
				root = root.RightRotate()
			}else{
				root = root.LRRoate()
			}

	}else if balance == -2{
		if root.right.left == nil || ( root.right.right != nil && root.right.right.height > root.right.left.height){
				root = root.LeftRotate()
			}else{
				root = root.RLRoate()
		}
	}
	//计算 叶子结点返回给到 我的高度 然后通过 max{root.right,root.left} + 1 计算出本节点的高度
	root.height = Max(left,right) + 1
	//返回本节点的高度 告诉给上一层
	return root
}


func (avlnode *node) RightRotate() *node{
	tmp :=avlnode.left
	avlnode.left = tmp.right
	tmp.right = avlnode
	return tmp
}
func (avlnode *node) LeftRotate() *node{
	tmp := avlnode.right
	avlnode.right = tmp.left
	tmp.left = avlnode
	return tmp
}

func (avlnode *node) LRRoate() *node{
	avlnode.left = avlnode.left.LeftRotate()
	return avlnode.RightRotate()
}

func (avlnode *node) RLRoate() *node{
	avlnode.right = avlnode.right.RightRotate()
	return avlnode.LeftRotate()
}
//func adjust(root *node) *node{
//	if root == nil{
//		return nil
//	}
//	lnode :=  adjust(root.left)
//	rnode :=  adjust(root.right)
//	var balancefactor int
//	//左右都为空 为叶子节点
//	if lnode == nil && rnode == nil{
//		return root
//	}
//	if lnode == nil{
//		//如果左边为空 计算右边节点的高度
//		 balancefactor =  0 - rnode.height
//	}else if rnode == nil{
//		 balancefactor = lnode.height - 0
//	}
//	fmt.Println("xxx",balancefactor)
//
//	//如果平衡因子 = +-2 了,进行调整
//	//等于 2 的话 树 向右边倾斜 需要右旋 -2向右边倾斜 需要 左旋
//	if balancefactor == 2{
//		root = root.RightRotate()
//	}else if balancefactor == -2{
//		root =root.LeftRotate()
//	}
//	return root
//}

//找到最小节点的上一个节点
func findMin(avlnode *node) *node{
	if avlnode.left == nil{
		return nil
	}
	min := findMin(avlnode.left)
	if min == nil{
		return avlnode
	}
	return avlnode
}
func delelte(avlnode *node,key int) *node{
	if avlnode == nil{
		return nil
	}
	if  avlnode.Key > key{
		avlnode.left =delelte(avlnode.left,key)
	}else if avlnode.Key < key{
		avlnode.right = delelte(avlnode.right,key)
	}else{
		if avlnode.left != nil && avlnode.right != nil {
			//如果 右边节点已进是页子节点
			if avlnode.right.left == nil {
				//替换成 右边的节点的Key
				avlnode.Key = avlnode.right.Key
				//替换成 右边的节点的右子树
				avlnode.right = avlnode.right.right
				//上面2 步 后 当前节点就变成了右节点
			} else {
				//找到 最小节点的上面一个节点
				nodep := findMin(avlnode.right)
				//最小的一个节点
				minnode := nodep.left
				//处理左边最小节点 可能拥有的右节点
				nodep.left = nodep.left.right
				//替换 成最小一个节点的值
				avlnode.Key = minnode.Key
			}
			//一遍 为 nil 直接返回 另一边
		}else if avlnode.left == nil{
			avlnode = avlnode.right
		}else if avlnode.right == nil{
			avlnode = avlnode.left
		}
	}
	return avlnode
}

//中序遍历
func (avlt *avltree) Delete(key int){
	avlt.root = delelte(avlt.root,key)
	avlt.root = Adjust(avlt.root)
}
//中序遍历
func (avlt *avltree) InorderTraversal(){
	inordertraversal(avlt.root)

}
func inordertraversal(root *node){
	if root == nil{
		return
	}
	inordertraversal(root.left)
	fmt.Println(root.Key)
	inordertraversal(root.right)
}

func (avlt *avltree) Find(key int) *node{
	return find(avlt.root,key)
}
func find(avlnode *node,key int) *node{
	if avlnode == nil{
		return nil
	}
	if avlnode.Key > key{
		ret := find(avlnode.left,key)
		return ret
	}else if avlnode.Key < key{
		ret :=find(avlnode.right,key)
		return ret
	}else{
		return avlnode
	}
}
func main(){
	a := NewAvTree()

	a.Insert(50)

	a.Insert(66)
	a.Insert(40)
	a.Insert(21)
	a.Insert(60)
	a.Insert(86)
	a.Insert(55)
	a.Insert(12)
	a.Insert(70)
	a.Insert(31)
	a.Insert(80)
	fmt.Println(a.Find(12))
	//a.InorderTraversal()
	a.Delete(12)
	fmt.Println(a.Find(12))
	a.InorderTraversal()


}
/**
 * Created by @CaomaoBoy on 2020/3/24.
 *  email:<[email protected]>
 */

总结:上面为了 方便说明学习,只是用int 实现 比较,实际上 可以传入任何类型的Key值 ,只要相应的提供一个比较函数即可。AVL树由于是严格平衡的所以在查询效率上 比红黑树要高但是每一次插入 都要自动平衡 修改 也要调整 节点再 次 平衡所以在修改上 AVL树性能不如红黑树.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章