普通二叉搜索树可能出现一条分支有多层,而其余分支却只有几层的状况,如图1所示,这会致使添加、移除和搜索树具备性能问题。所以提出了自平衡二叉树的概念,AVL树(阿德尔森-维尔斯和兰迪斯树)是自平衡二叉树的一种,AVL树的任一子节点的左右两侧子树的高度之差不超过1,因此它也被称为高度平衡树。node
要将不平衡的二叉搜索树转换为平衡的AVL树须要对树进行一次或屡次旋转,旋转方式分为左单旋、右单旋、左-右双旋、右-左双旋。segmentfault
对某一节点B(图2)作左单旋,处理过程至关于,断开B与父节点A的链接,将B的右子节点D与A链接,将B做为D的左子节点,将D的左子节点E做为B的右子节点。以图1的二叉树为例,对键值为15的节点作左单旋,首先断开15与11的链接,再将20与11链接,将15做为20的左子节点,最后将18做为15的右子节点;能够想象为以15为中心作了必定的逆时针旋转。结果如图3。函数
再看图2,根据搜索二叉树的性质,确定有D>B>A,E>B,所以旋转事后,可以保证 右子节点 > 父节点 > 左子节点,不会破坏树的结构。
能够看到,一次左单旋将右侧子树的高度减少了1,而左侧子树的高度增长了1。实现代码以下:性能
function roateLeft(AvlNode) { var node = AvlNode.right; // 保存右子节点 AvlNode.right = node.left; // node的左子节点链接到AvlNode成为其右子节点 node.left = AvlNode; // AvlNode链接到node成为其左子节点 return node; // 返回node,链接到AvlNode最初的父节点 }
右单旋与左单选相似,以某一节点B(图4)作右单旋,首先断开B与其父节点A的链接,将B的左子节点C与A链接,将C的右子节点F做为B的左子节点。一样的,由于有C>A,B>F>C,所以旋转事后,不会破坏树的结构。能够看到,一次右单旋使节点的左侧子树高度减少了1,而右侧子树的高度增长了1。spa
实现代码以下:3d
function roateRight(AvlNode) { var node = AvlNode.left; // 保存左子节点 AvlNode.left = node.right; // 将node的右子节点链接到AvlNode成为其左子节点 node.right = AvlNode; // AvlNode链接到node,成为其右子节点 return node; // 返回node链接到AvlNode最初的父节点 }
左单旋、右单旋在某些状况下是不能达到平衡树的目的的。如图4,对B进行右单旋,须要左子树C的右子树F的高度小于等于左子树E的高度,不然不能达到平衡的效果,只是把不平衡性从左边转移到了右边。图5演示了这种状况。一样的,左单旋也有这个问题。code
所以为了达到目的,须要先对旋转节点的左子节点作左单旋,再对旋转节点作右单旋。如图6所示,先对节点B的左子节点C作左单旋,能够看到,这个操做,至关于将节点C的不平衡性从右侧转移到了左侧,从而知足了上述右单旋的条件;最后再对B节点作右单旋操做,最终达到了平衡的目的。blog
实现代码以下:ip
function roateLeftRight(AvlNode) { AvlNode.right = roateLeft(AvlNode.right); // 对右子节点作左单旋 return roateRight(AvlNode); // 作右单旋 }
同理,如图2,对B进行左单旋时,须要右子树D的右子树F的高度大于等于左子树E的高度,不然须要进行双旋;即先对B的右子节点D作右单旋,再对B作左单旋。实现代码以下:get
function roateRightLeft(AvlNode) { AvlNode.left = roateRight(AvlNode.left); // 对左子节点作右单旋 return roateLeft(AvlNode); // 作左单旋 }
首先实现获取树高度的函数:
function getAvlTreeHeight(node) { if (node == null) { // node不存在返回0 return 0; } else { var leftHeight = getAvlTreeHeight(node.left); var rightHeight = getAvlTreeHeight(node.right); // 返回左子树、右子树中的最大高度 return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1; } }
实现平衡树的函数:
function balance(node) { if (node == null) { return node; } // 左子树高度比右子树高度大1以上 if (getAvlTreeHeight(node.left) - getAvlTreeHeight(node.right) > 1) { if (getAvlTreeHeight(node.left.left) >= getAvlTreeHeight(node.left.right)) { // 若是左子树的左子树高度大于等于左子树的右子树高度 // 直接进行右单旋 node = roateRight(node); } else { // 不然须要右-左双旋 node = roateRightLeft(node); } // 右子树高度比左子树高度大1以上 } else if (getAvlTreeHeight(node.right) - getAvlTreeHeight(node.left) > 1) { if (getAvlTreeHeight(node.right.right) >= getAvlTreeHeight(node.right.left)) { // 若是右子树的右子树高度大于等于右子树的左子树高度 // 直接进行左单旋 node = roateLeft(node); } else { // 不然须要左-右双旋 node = roateLeftRight(node); } } return node; }
在二叉搜索树的基础上,每次插入节点,都须要作一次树的平衡处理:
var insertNode = function(node, newNode){ if (newNode.key < node.key){ if (node.left === null){ node.left = newNode; // 插入节点后,作树的平衡处理 node.left = balance(node.left); } else { insertNode(node.left, newNode); } } else { if (node.right === null){ node.right = newNode; // 插入节点后,作树的平衡处理 node.right = balance(node.right); } else { insertNode(node.right, newNode); } } }
综上,一颗自平衡AVL树的原理及实现就完成了。