AVL 树旋转及 JS 实现,平衡树支棱起来~

简介: 此“树”不是一般的“树”!它在 1962 年被发明,作者是 G. M. Adelson-Velsky 和 Evgenii Landis,AVL 树是最早的平衡二叉树实现之一。本篇将继续探索 AVL 树基础原理,日拱一卒,冲!

image.png

此“树”不是一般的“树”!它在 1962 年被发明,作者是 G. M. Adelson-VelskyEvgenii LandisAVL 树是最早的平衡二叉树实现之一。


本篇将继续探索 AVL 树基础原理,日拱一卒,冲!

推荐阅读:Self-balanced Binary Search Trees with AVL in JavaScript (挖个坑,有空翻译~)


AVL旋转



在 AVL 树中,增加和删除元素的操作则可能需要借由一次或多次 树旋转,以实现树的重新平衡。


所以,AVL树最核心操作就是“AVL 旋转”!


以下 GIF 演示了不断将节点插入AVL树时的情况,包含:

  • 左旋(Left Rotation)
  • 右旋(Right Rotation)
  • 右左旋转(Right-Left Rotation)
  • 左右旋转(Left-Right Rotation)
  • 以及带子树的右旋(Right Rotation with children)

image.png


安利一个在线动态演示 VAL 树的旋转的网站:www.cs.usfca.edu/~galles/vis… 👍👍👍


image.png


png 示意:

image.png


(图片来源:wikipedia


AVL 的操作代价分析:


  1. 查找代价:查找效率很好,最坏情况都是O(logN)数量级;
  2. 插入代价: AVL必须要保证严格平衡(|bf|<=1),那么每一次插入数据使得AVL中某些结点的平衡因子超过1就必须进行旋转操作。事实上,AVL的每一次插入结点操作最多只需要旋转1次(单旋转或双旋转)。因此,总体上插入操作的代价仍然在O(logN)级别(插入结点需要首先查找插入的位置);
  3. 删除代价:删除必须检查从删除结点开始到根结点路径上的所有结点的平衡因子。因此删除的代价稍微要大一些。每一次删除操作最多需要O(logN)次旋转。因此,删除操作的时间复杂度为O(logN)+O(logN)=O(2logN);


JS 实现



左单旋:

function roateLeft(AvlNode) {
        var node = AvlNode.right; // 保存右子节点
        AvlNode.right = node.left; // node的左子节点连接到AvlNode成为其右子节点
        node.left = AvlNode; // AvlNode连接到node成为其左子节点
        return node; // 返回node,连接到AvlNode最初的父节点
}


右单旋:

function roateRight(AvlNode) {
        var node = AvlNode.left; // 保存左子节点
        AvlNode.left = node.right; // 将node的右子节点连接到AvlNode成为其左子节点
        node.right = AvlNode; // AvlNode连接到node,成为其右子节点
        return node; // 返回node连接到AvlNode最初的父节点
}


左-右双旋:

function roateLeftRight(AvlNode) {
        AvlNode.right = roateLeft(AvlNode.right); // 对右子节点做左单旋
        return roateRight(AvlNode); // 做右单旋
}


右-左双旋:

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);
        }
    }
}


u1s1,当树开始旋转,脑袋也有点晕眩了╮(╯▽╰)╭

啃不下来,就先收藏慢慢啃吧~~

不慌,后续还会带来更多关于平衡二叉树的练习,以及前端少有接触的红黑树等等。。。


OK,以上就是本篇分享~ 撰文不易,点赞鼓励👍👍👍


我是掘金安东尼,公众号同名,日拱一卒、日掘一金,再会~


参考:



相关文章
|
前端开发
|
2月前
|
算法 JavaScript
JS 【详解】树的遍历(含深度优先遍历和广度优先遍历的算法实现)
JS 【详解】树的遍历(含深度优先遍历和广度优先遍历的算法实现)
51 0
JS 【详解】树的遍历(含深度优先遍历和广度优先遍历的算法实现)
|
3月前
|
安全 JavaScript
旋转木马轮播图 html+css+js
旋转木马轮播图 html+css+js
|
3月前
|
JavaScript 前端开发 算法
虚拟DOM是React的关键技术,它是个轻量的JS对象树,模拟实际DOM结构。
【6月更文挑战第27天】虚拟DOM是React的关键技术,它是个轻量的JS对象树,模拟实际DOM结构。当状态改变,React不直接修改DOM,而是先构建新的虚拟DOM树。通过 diff 算法比较新旧树,找到最小变更,仅更新必要部分,提高性能,避免频繁DOM操作。虚拟DOM还支持跨平台应用,如React Native。它优化了更新流程,简化开发,并提升了用户体验。
33 1
|
2月前
|
JavaScript
js 解析和操作树 —— 获取树的深度、提取并统计树的所有的节点和叶子节点、添加节点、修改节点、删除节点
js 解析和操作树 —— 获取树的深度、提取并统计树的所有的节点和叶子节点、添加节点、修改节点、删除节点
80 0
|
4月前
|
前端开发 JavaScript 算法
JavaScript 中实现常见数据结构:栈、队列与树
JavaScript 中实现常见数据结构:栈、队列与树
|
4月前
|
JavaScript
理解DOM树的加载过程(js的问题)
理解DOM树的加载过程(js的问题)
24 0
|
JavaScript
使用JS 实现二叉查找树(Binary Search Tree)
使用JS 实现二叉查找树(Binary Search Tree)
68 0
|
4月前
|
JSON JavaScript 前端开发
js(递归函数)实现树型菜单
js(递归函数)实现树型菜单
49 0
antd+js 目录树实现
antd+js 目录树实现
92 0