AVL树
简介
在计算机科学中,AVL树是最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。
AVL树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。
图例
每个节点的左右子树高度差不大于1。
节点 5
的左右子树高度差大于 1。
意义
许多二叉搜索树的操作(例如:搜索、插入、删除、最大值、最小值等)需要 $O(h)$ 的时间复杂度,其中 $h$ 为树的高度。最差的情况遇到完全倾斜的树,时间复杂度会变为 $O(n)$ 。如果每次插入或删除节点后都能保证树的高度为 $O(\log{n})$ 的话,那就可以使得时间复杂度上限变为 $O(\log{n})$。使用 AVL树可以使树的高度保持为 $O(\log{n})$。
复杂度
算法 |
平均 |
最差 |
空间 |
$O(n)$ |
$O(n)$ |
搜索 |
$O(\log{n})$ |
$O(\log{n})$ |
插入 |
$O(\log{n})$ |
$O(\log{n})$ |
删除 |
$O(\log{n})$ |
$O(\log{n})$ |
树旋转
简介
在对二叉搜索树进行插入或删除操作后,可能会造成树的不平衡,所以需要对不平衡的节点进行旋转操作来保持平衡。
树旋转(英语:Tree rotation)是对二叉树的一种操作,不影响元素的顺序(二叉搜索树,但会改变树的结构。
一般有两种旋转方式:
如图所示:
旋转后仍可以保证 $T1 < x < T2 < y < T3$。
维基百科上的动画表达更加清晰:
由Tar-Elessar - 自己的作品,CC BY-SA 4.0,https://commons.wikimedia.org/w/index.php?curid=34521454
需要进行旋转的情况
当执行完二叉搜索树的节点插入或删除后,有四种情况会出现树不平衡,需要进行旋转操作:
下面使用图片说明各种情况:
左左情况
节点 z
的左右子树高度差大于 1,处于不平衡状态。需要右旋转。
左右情况
首先将节点 y
进行左旋转,变成左左情况,再进行右旋转。
右右情况
节点 z
的左右子树高度差大于 1,处于不平衡状态。需要左旋转。
右左情况
先将节点 y
进行右旋转,变成右右情况,再进行左旋转。
实现(JavaScript)
AVL树类
1 2 3 4 5 6 7 8
| class AVLTreeNode { constructor(val) { this.val = val; this.left = null; this.right = null; this.height = 1; } }
|
左旋转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
function leftRotate(x) { const y = x.right; const t2 = y.left;
y.left = x; x.right = t2;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
return y; }
|
右旋转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
function rightRotate(y) { const x = y.left; const t2 = x.right; x.right = y; y.left = t2;
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x; }
|
插入节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
function insert(root, val) { if(!root) { return new AVLTreeNode(val); } if(val < root.val) { root.left = insert(root.left, val); } else if(val > root.val) { root.right = insert(root.right, val); } else { return root; }
root.height = 1 + Math.max(getHeight(root.left), getHeight(root.right));
return balanceRoot(root); }
|
删除节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
function deleteNode(root, val) { if (!root) { return root; } if ( val < root.val ) { root.left = deleteNode(root.left, val); } else if( val > root.val ) { root.right = deleteNode(root.right, val);
} else { if(!root.left || !root.right) { let temp = root.left ? root.left : root.right; if (!temp) { temp = root; root = null; } else { root = temp; } } else { const temp = minValueNode(root.right); root.val = temp.val; root.right = deleteNode(root.right, temp.val); } } if (!root) { return root; } root.height = 1 + max(getHeight(root.left), getHeight(root.right)); return balanceRoot(root); }
|
平衡处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
function balanceRoot(root) { const balance = getBalance(root);
if(balance > 1 && val < root.left.val) { return rightRotate(root); }
if(balance < -1 && val > root.right.val) { return leftRotate(root); }
if(balance > 1 && val > root.left.val) { root.left = leftRotate(root.left); return rightRotate(root); }
if(balance < -1 && val < root.right.val) { root.right = rightRotate(root.right); return leftRotate(root); }
return root; }
|
辅助函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
function getHeight(n) { if(!n) { return 0; } return n.height; }
function getBalance(n) { if(!n) { return 0; } return getHeight(n.left) - getHeight(n.right); }
function minValueNode(node) { let p = node; while(p.left) { p = p.left; } return p; }
|
复杂度分析
- 左右旋转操作:$O(1)$,因为只操作了节点指针。
- 更新节点高度和获取平衡因子:$O(1)$。
- 插入节点:$O(h)$ ,$h$ 为树的高度。因为 AVL 树是平衡二叉树,高度为 $h = \log{n}$,所以复杂度为 $O(\log{n})$。
- 删除节点:同插入节点为 $O(h)$。
参考链接