AVLTree 一种自平衡的二叉查找树

什么是平衡树

对于已有的二叉查找树,它最坏情况下会退化为链表,查找效率降至O(n),我们希望的是插入或者删除元素始终能使得维持成完全二叉树的样子(完全二叉树n个节点,其高度为logn)这样查找效率就能维持在logn。

平衡树字面意义上就是说让树的两边看起来是均匀的,只要满足任意节点的左子树和右子树高度差不大于1,那么就称这棵树为平衡树。AVL树在平衡树的基础上还满足二叉查找树的性质,所以AVL树也称为自平衡二叉查找树。

AVL树的旋转

想维护一棵AVL树,必然需要在高度失衡时对节点进行调整,使得高度差小于1,这里就需要用到树的旋转。当失衡时,树的旋转只有4中情况,下面我们根据具体例子来介绍这四种旋转。

LL操作

当1插入进来,树的平衡被破坏,此时我们可以发现,节点3的高度差为-2,且节点3是最早发现平衡被破坏的节点,我们把这种节点叫做“发现节点”,有的博文也称其为“最小失衡子树根节点”。我们把节点1称为“麻烦节点”,因为1的插入导致我们必须对树进行调整。

调整方式非常简单,直接将节点2提升为根节点即可。“麻烦节点”在发现节点的左子树的左子树上,这种情况我们称之为LL操作。

对于一般情况,该图可以更好描述节点的变动。橙色节点为新插入的麻烦节点,红色节点为发现节点。

RR操作

麻烦节点在发现节点的右子树的右子树上,调整方式称之为RR操作。

LR操作

麻烦节点在发现节点的左子树的右子树上,调整方式称之为LR操作。这里节点C的左右子树任意一侧的插入都可能导致失衡,表示D或E节点的插入。
对于BAC节点来说,调整的结果要保证有序,要维护搜索树的性质。这里A<C<B,所以我们将C节点提升为根节点。其余节点根据搜索树性质进行移动。

RL操作

和LR操作是对称的。

AVL树的实现

AVL树最常用的是插入、查找、删除三种操作。查找时间为logn,插入和删除均需要先找到插入位置,该时间为logn,但可能需要多次调整,每次调整时间为O(1)。整体可以认为插入和删除时间复杂度为logn。

每次插入和搜索树一样,但是需要在插入之后进行校验调整。而删除操作有些麻烦,删除右子树节点,相当于在左子树插入一个节点,要重新调整左子树的平衡,比如下图中要删除节点7,相当于在左子树插入了节点1,需要做RR操作。

注意:
在上面的介绍中,LL操作表示左子树的左子树麻烦节点的调整,对应代码中的rightRotation函数,这是因为LL操作的旋转方式可以想象成向右旋转,这里的操作命名和实际旋转方向需要注意分辨,代码中的命名是按照旋转方向命名的,而不是麻烦节点的位置。(其实是我写完代码后懒得改了...QAQ)

package test_ds

type AVLTree struct {
	root *aVLTreeNode
}

func NewAVLTree() *AVLTree {
	return &AVLTree{}
}

func (t *AVLTree) Put(key int, value interface{}) {
	t.root = insert(t.root, key, value)
}

func (t *AVLTree) Remove(key int) {
	t.root = remove(t.root, key)
}

func (t *AVLTree) Get(key int) interface{} {
	if n := find(t.root, key); n != nil {
		return n.value
	}
	return nil
}

func (t *AVLTree) GetNode(key int) *aVLTreeNode {
	return find(t.root, key)
}

type aVLTreeNode struct {
	key    int
	value  interface{}
	height int
	left   *aVLTreeNode
	right  *aVLTreeNode
}

func newAVLNode(key int, value interface{}, left, right *aVLTreeNode) *aVLTreeNode {
	return &aVLTreeNode{
		key:    key,
		value:  value,
		height: 1,
		left:   left,
		right:  right,
	}
}

// 插入节点
func insert(cur *aVLTreeNode, key int, value interface{}) *aVLTreeNode {
	if cur == nil {
		cur = newAVLNode(key, value, nil, nil)
		return cur
		//cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1
	}

	if key == cur.key {
		cur.value = value
		//return cur
	} else if key > cur.key {
		cur.right = insert(cur.right, key, value)
		if getHeight(cur.right)-getHeight(cur.left) == 2 {
			if key > cur.right.key {
				cur = leftRotation(cur) // 情况1:需要单左旋
			} else {
				cur = rightLeftRotation(cur) // 情况2:需要右旋再左旋
			}
		}
	} else {
		cur.left = insert(cur.left, key, value)
		if getHeight(cur.left)-getHeight(cur.right) == 2 {
			if key < cur.left.key {
				cur = rightRotation(cur) // 情况3:需要单右旋
			} else {
				cur = leftRightRotation(cur) // 情况4:需要左旋再右旋
			}
		}
	}

	cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1

	return cur
}

// 删除节点
func remove(cur *aVLTreeNode, key int) *aVLTreeNode {
	if cur == nil {
		return nil
	}

	if key == cur.key {

		// 存在左右子树
		if cur.left != nil && cur.right != nil {

			// 左子树高,通过赋值删除左子树最大值节点,维护树的有序
			if getHeight(cur.left) > getHeight(cur.right) {
				m := getMax(cur.left)
				cur.key, cur.value = m.key, m.value
				cur.left = remove(cur.left, m.key)
				//cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1
			} else {
				// 右子树高,删右子树最小值节点
				m := getMin(cur.right)
				cur.key, cur.value = m.key, m.value
				cur.right = remove(cur.right, m.key)
				//cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1
			}
		} else {
			// 只有一个子树或无子树
			if cur.left != nil {
				cur = cur.left
			} else if cur.right != nil {
				cur = cur.right
			} else {
				cur = nil
			}
		}
	} else if key > cur.key {

		// 删除右子树节点,相当于在左子树插入节点
		cur.right = remove(cur.right, key)

		// 失衡调整
		if getHeight(cur.left)-getHeight(cur.right) == 2 {
			if getHeight(cur.left.right) > getHeight(cur.left.left) {
				cur = leftRightRotation(cur)
			} else {
				cur = rightRotation(cur) // 相当于情况3、4
			}
		} else {
			cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1
		}
		//cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1
	} else {

		// 删除左子树节点,相当于在右子树插入节点
		cur.left = remove(cur.left, key)

		// 失衡调整
		if getHeight(cur.right)-getHeight(cur.left) == 2 {
			if getHeight(cur.right.left) > getHeight(cur.right.right) {
				cur = rightLeftRotation(cur)
			} else {
				cur = leftRotation(cur) // 相当于情况1、2
			}
		} else {
			cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1
		}
		//cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1
	}

	return cur
}

// 查找节点
func find(cur *aVLTreeNode, key int) *aVLTreeNode {
	if cur == nil {
		return nil
	}
	if key == cur.key {
		return cur
	} else if key > cur.key {
		return find(cur.right, key)
	} else {
		return find(cur.left, key)
	}
}

// 单左旋操作
// 4              5
//  \            / \
//   5    ->    4   6
//    \
//     6
// 参数cur为最小失衡子树的根节点,在图中为节点4
// 若节点5有左子树,则该左子树成为节点4的右子树
// 节点4成为节点5的左子树
// 最后更新节点的高度值
func leftRotation(n *aVLTreeNode) *aVLTreeNode {
	cur := n.right
	n.right = cur.left
	cur.left = n

	n.height = max(getHeight(n.left), getHeight(n.right)) + 1
	cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1

	return cur
}

// 镜像的单右旋操作
func rightRotation(n *aVLTreeNode) *aVLTreeNode {
	cur := n.left
	n.left = cur.right
	cur.right = n

	n.height = max(getHeight(n.left), getHeight(n.right)) + 1
	cur.height = max(getHeight(cur.left), getHeight(cur.right)) + 1

	return cur
}

// 先右旋再左旋
func rightLeftRotation(n *aVLTreeNode) *aVLTreeNode {
	n.right = rightRotation(n.right)
	return leftRotation(n)
}

// 先左旋再右旋
//        5                   5                    5
//       / \                 / \                  / \
//      3   7               3   7                3   7
//     / \ / \             / \ / \              / \ / \
//    2  4 6  8    ->     2  4 6  8     ->     1  4 6  8
//   /                   /                    / \
//  0                   1                    0   2
//   \                 /
//    1               0
// 图中2为最小失衡子树的根节点
// 将2的右左子树进行左旋
// 再将以2为根节点的子树右旋
func leftRightRotation(n *aVLTreeNode) *aVLTreeNode {
	n.left = leftRotation(n.left)
	return rightRotation(n)
}

// 获取节点高度
func getHeight(n *aVLTreeNode) int {
	if n == nil {
		return 0
	}
        return n.height
	// return max(getHeight(n.left), getHeight(n.right)) + 1
}

// 子树中最大值节点
func getMax(n *aVLTreeNode) *aVLTreeNode {
	if n.right != nil {
		return getMax(n.right)
	}
	return n
}

// 子树中最小值节点
func getMin(n *aVLTreeNode) *aVLTreeNode {
	if n.left != nil {
		return getMin(n.left)
	}
	return n
}

func max(v1, v2 int) int {
	if v1 > v2 {
		return v1
	} else {
		return v2
	}
}

AVL树的删除

在AVL的众多操作中,删除操作是最麻烦也是最不好理解、最容易出错的操作。网上有很多关于删除操作的代码是错误的,我经过大量测试,在上方贴出了正确实现。其中的奥妙在于这两处判断:

if getHeight(cur.right.left) > getHeight(cur.right.right) {
    cur = rightLeftRotation(cur)
} else {
    cur = leftRotation(cur)
}

if getHeight(cur.left.right) > getHeight(cur.left.left) {
    cur = leftRightRotation(cur)
} else {
    cur = rightRotation(cur)
}

它表示当删除某一节点后发现高度失衡,进而对应上述4种插入的情况进行树的调整。但是你有没有想过为什么直接将getHeight(cur.right.left) == getHeight(cur.right.right)的情况囊括在第二个判断?如果出现==的情况,进行RR或LL操作可以解决失衡吗?

我们看个例子:
如果我们先做RR操作,一次就可以成功。如果做RL操作就会导致进一步的树失衡,需要再次调整。

可能我们会觉得,大不了就多做一次旋转嘛,无非就是在写一行代码:

if getHeight(cur.right.left) > getHeight(cur.right.right) {
    cur = rightLeftRotation(cur)
} else if getHeight(cur.right.left) < getHeight(cur.right.right) {
    cur = leftRotation(cur)
} else {
    cur = rightLeftRotation(cur)
    cur.right = leftRotation(cur.left)
}

if getHeight(cur.left.right) > getHeight(cur.left.left) {
    cur = leftRightRotation(cur)
} else if getHeight(cur.left.right) < getHeight(cur.left.left) {
    cur = rightRotation(cur)
} else {
    // 和上面相反:
    cur = leftRightRotation(cur) // LR
    cur.left = rightRotation(cur.left) // LL
}

但是问题随之而来,这种写法真的具有普适性吗?我们看下一个例子,如果按照上述修改后的代码操作,就会再次引发失衡,可见上述代码不具备普适性。如果想获取普适性,就需要将 == 的情况,囊括进第二个判断中,出现==的情况,直接进行RR或LL即可。

参考:
https://www.icourse163.org/learn/ZJU-93001?tid=1459700443#/learn/content
https://www.cnblogs.com/WindSun/p/11379670.html

posted @ 2021-12-21 15:24  moon_orange  阅读(48)  评论(0编辑  收藏  举报