平衡二叉搜索树
在理想情况下,二叉搜索树添加、删除、搜索的时间复杂度为 logn 级别,但是二叉搜索树的结构依赖于添加的顺序。最糟糕的情况会让一棵二叉搜索树退化成一个链表。
对于7、4、9、2、5、8、11这样的数据,构造出来的二叉搜索树是这样的:
但是如果数据添加的顺序不是按照这个顺序而是按照从大到小/从小到大的顺序,那么就会退化成一个链表。
对于二叉搜素树来说,由于数据添加的顺序决定了树的结构,所以很难达到理想情况。
平衡: 所谓的平衡就是左右子树的高度接近。左右子树高度越接近,二叉树越平衡。
最理想的平衡就是像完全二叉树那样,高度是最小的。
在二叉搜索树的添加、删除之后做一些操作,来减小树的高度,让树变平衡一些。
AVL树
AVL树是最早发明的平衡二叉搜索树之一。
平衡因子
平衡因子是AVL树中一个比较重要的概念,它是指树中某个节点的左右子树的高度差,通常我们用左子树减去右子树。
AVL树对平衡因子的要求:每个节点的平衡因子只能为 -1,0,1。
这意味着每个节点的左右子树的高度差不能超过1,超过就意味着失去平衡需要调整。
AVL树节点定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class AVLNode <T> extends TreeNode <T> { public height : number = 1 ; public right : AVLNode <T> | null = null ; public left : AVLNode <T> | null = null ; public parent : AVLNode <T> | null = null ; public get balanceFactor () { const left = this .left ?.height || 0 ; const right = this .right ?.height || 0 ; return left - right; } }
失衡调整
AVL树失衡的调整有四种情况,添加和删除是一样的。
LL型
这种情况下,对于失衡的节点来说,是左子树的左子树导致的。也就是说左子树的高度比右子树的高度 > 1,而且左子树的左子树的高度是大于左子树的右子树的。
以添加到情况为例,添加了节点2导致了失衡,失衡的节点是9,而导致失衡的原因是因为左子树的左子树导致的。
调整方式: 让失衡节点右单旋。
以上面的情况为例:
对于一般的情况就是这样:
RR型
和LL型对应,就是右子树的右子树导致的节点失衡。
调整方案也很简单,让失衡的节点左单旋。
LR型
是左子树的右子树导致的失衡。
调整方案:
左子树根节点左单旋
失衡节点右单旋
RL型
是右子树的左子树导致的失衡。
调整方案:
右子树根节点右单旋
失衡节点左单旋
实现旋转
可以看到核心的逻辑就是左单旋和右单旋。
左单旋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function rotateLeft (root : AVLNode <T> ): AVLNode <T> { const right = <AVLNode <T>>root.right ; const rightLeft = right.left ; right.left = root; root.right = rightLeft; right.parent = root.parent ; rightLeft && (rightLeft.parent = root); root.parent = right; this .updateHeight (root); this .updateHeight (right); return right; }
右单旋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function rotateRight (root : AVLNode <T> ): AVLNode <T> { const left = <AVLNode <T>>root.left ; const leftRight = left.right ; left.right = root; root.left = leftRight; left.parent = root.parent ; leftRight && (leftRight.parent = root); root.parent = left; updateHeight (root); updateHeight (left); return left; }
其中的 updateHeight 方法:
1 2 3 function updateHeight (root : AVLNode <T> ): void { root.height = Math .max (root.right ?.height || 0 , root.left ?.height || 0 ) + 1 ; }
添加和删除
AVL树的添加和删除需要在二叉搜索树的基础上稍加改进。
因为添加和删除会导致树失衡,所以添加和删除完之后可以直接检查一个节点是否失衡并调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function checkBalance (root : AVLNode <T> ): AVLNode <T> { const factor = root.balanceFactor ; if (factor > 1 ) { const left = <AVLNode <T>>root.left ; const leftFactor = left.balanceFactor ; if (leftFactor > 0 ) { root = rotateRight (root); } else { root.left = this .rotateLeft (left); root = rotateRight (root); } } else if (factor < -1 ) { const right = <AVLNode <T>>root.right ; const rightFactor = right.balanceFactor ; if (rightFactor < 0 ) { root = rotateLeft (root); } else { root.right = rotateRight (right); root = rotateLeft (root); } } return root; }
添加:
1 2 3 4 5 6 7 function add (root : AVLNode <T> | null , val : T ): AVLNode <T> { this .updateHeight (root); root = this .checkBalance (root); return root; }
删除:
1 2 3 4 5 6 7 8 function delete (root : AVLNode <T> | null , val : T ): AVLNode <T> | null { if (root) { this .updateHeight (root); root = this .checkBalance (root); } return root; }
注: 检测一个节点是否平衡的逻辑大致相同,但是对于不同的添加方式,迭代和递归的最终代码会有所差别,因为递归的特性只需要在每次添加/删除完成更新高度并检测就行。
tips: