学习JavaScript数据结构与算法 — AVL树

AVL树

普通二叉搜索树可能出现一条分支有多层,而其余分支却只有几层的状况,如图1所示,这会致使添加、移除和搜索树具备性能问题。所以提出了自平衡二叉树的概念,AVL树(阿德尔森-维尔斯和兰迪斯树)是自平衡二叉树的一种,AVL树的任一子节点的左右两侧子树的高度之差不超过1,因此它也被称为高度平衡树。node

图1

clipboard.png

要将不平衡的二叉搜索树转换为平衡的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

clipboard.png

图3

clipboard.png

再看图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

图4

clipboard.png

实现代码以下: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

图5

clipboard.png

所以为了达到目的,须要先对旋转节点的左子节点作左单旋,再对旋转节点作右单旋。如图6所示,先对节点B的左子节点C作左单旋,能够看到,这个操做,至关于将节点C的不平衡性从右侧转移到了左侧,从而知足了上述右单旋的条件;最后再对B节点作右单旋操做,最终达到了平衡的目的。blog

图6

clipboard.png

实现代码以下: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树的原理及实现就完成了。

相关文章
相关标签/搜索