企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 介绍 1. AVL 树是最早被发明的自平衡二叉树 2. AVL 树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文中发表了它。 # 构建 AVL 树 为了保证一树是时刻保持平衡,我们需要在向节点中插入、删除新节点时通过 `旋转` 来调整树的结构。 如何实现二叉树调整平衡,要通过二叉树的 `旋转` 来实现。 二叉树可以分为四种情况,不同的情况进行不同的旋转: L:left 左 LL: 左左型 R:right 右 RR:右右型 LR:左右型 RL:右左型 * LL型二叉树 :右旋转 * RR型二叉树:左旋转 * LR型二叉树:先左旋转再右旋转 * RL型二叉树:先右旋转再左旋转 ## LL 型 对于节点倾向于左边的情况,我们称之为 `LL型`。 这个时候我们需要进行 `右旋转` 操作,使它恢复平衡: ![](https://img.kancloud.cn/8a/a1/8aa15bcf24fcebe43cd13834a248d5a3_1564x382.png) 再看一个例子: ![](https://img.kancloud.cn/c6/94/c69429d90549a6b36e2fd04f63371af9_1610x454.png) 再看一个例子: ![](https://img.kancloud.cn/3f/52/3f52d1633dab9bfd6886677900145958_1590x484.png) ## RR 型 对于节点倾向于右边的情况,我们称之为 `RR型`。 这个时候我们需要进行 `左旋转` 操作,使它恢复平衡: ![](https://img.kancloud.cn/5c/3a/5c3a603ab01eaa71ac8e6fc305171d4d_1532x376.png) 再看一个例子: ![](https://img.kancloud.cn/0f/08/0f0894458395b53ff0febba95191ef60_1594x486.png) 再看一个例子: ![](https://img.kancloud.cn/ea/4c/ea4ca24c662330b2c046a14825a9ef93_1582x496.png) ## LR 型 以下树为 LR 型,这种情况需要旋转两次:先左旋转再右旋转: ![](https://img.kancloud.cn/85/92/8592803091b532a85c27faa120076a24_1618x324.png) 再看一个例子: ![](https://img.kancloud.cn/ae/da/aeda8afba4d36e42a639b4576fadd147_1642x380.png) 再看一个例子: ![](https://img.kancloud.cn/1a/6f/1a6ff680606e530bde5d9a13c08d419c_1610x378.png) ## RL 型 看一个例子: ![](https://img.kancloud.cn/a8/10/a8106ed98a2061021bd432a96b8adca0_1578x314.png) 再看一个例子: ![](https://img.kancloud.cn/12/4e/124e72f39c567b3fe7e9adbb8d4bfdad_1608x362.png) 再看一个例子: ![](https://img.kancloud.cn/af/6a/af6ab811a9c454bd9259907c0892928b_1608x360.png) # 代码实现 ## 平衡因子 总结: 1. 平衡因子:左子树的高度-右子树的高度 2. 如果平衡因子大于等于 2 说明这个节点不平衡 3. 如果平衡因子是正数,说明:L 型(左高) 4. 如果平衡因子是负数,说明:R 型(右高) ## 获取一个的高度 节点高度:左右节点中到叶子节点最多的边数。 ~~~ // 获取节点高度 getHeight(node) { if(node === null) return 0 // 左右节点中最长的边数 return Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1 } ~~~ ## 添加四种旋转方法 ### 左旋 ![](https://img.kancloud.cn/8d/08/8d08c7fc10a70e2752bf6f0420d93166_772x374.png) 代码: ![](https://img.kancloud.cn/4c/82/4c82438da017fa542fe09a94c02b90c1_480x380.png) ### 右旋 ![](https://img.kancloud.cn/52/0d/520d0162daedcc8b84f28fd6eb72eb70_754x350.png) 代码: ![](https://img.kancloud.cn/c2/40/c240a50100a67323c0d7753294e4bd3e_374x262.png) ### 先左后右 ![](https://img.kancloud.cn/06/a6/06a68bc9f191f71babfd14266967cf8c_794x280.png) 代码: ![](https://img.kancloud.cn/17/c4/17c4b959fee4200275c6415f5a48604d_632x256.png) ### 先右后左 ![](https://img.kancloud.cn/04/00/0400dd78b3f3eaaf509843000be7c65c_812x258.png) 代码: ![](https://img.kancloud.cn/c9/b6/c9b64a4d4cf8ff9d271a68fd4ab3cee3_628x264.png) ### 调整二叉树 计算一个节点的高度并判断是否平衡,如果不平衡,就判断是什么型并进行相应的旋转以保持平衡: ![](https://img.kancloud.cn/5d/2f/5d2f0f89b357abf47eb878fa076760d2_1416x1074.png) ### 在添加节点时检查并调整节点 在每次添加一个新节点时,都有可能打破平衡,需要检查并调整: 修改添加节点的代码,在向儿子节点中添加节点时要重新检查并调整 ![](https://img.kancloud.cn/8f/5a/8f5af9bf3b75880f65aba60d731cf6c3_918x1368.png) # 完整 AVL Tree 代码 ~~~ // 定义节点类 class Node { constructor(data) { this.data = data // 数据 this.left = null // 左子节点 this.right = null // 右子节点 } } // AVL树(自平衡二叉搜索树) class AVLTree { constructor() { this.root = null // 根节点 this.length = 0 // 树中节点的数量 } // 向树中插入一条记录 insert(data) { // 1. 创建新的节点 let newNode = new Node(data) // 2.放到树中 // 如果是空树 if (this.root === null) { // 设置这个新节点为树根 this.root = newNode } else { // 从根节点开始比较 this.root = this._insert(this.root, newNode) } } _insert(node, newNode) { if (node === null) { node = newNode return node } // 小于向左走 if (newNode.data < node.data) { // 判断是否有左子节点 if (node.left === null) { // 设置这个新节点为左子节点 node.left = newNode } else { // 递归,继承用新节点和左子节点比较 node.left = this._insert(node.left, newNode) // 重新检查并调整当前这个节点的平衡 node = this.keepBalance(node) } } else { // 判断是否有右子节点 if (node.right === null) { // 设置这个新节点为右子节点 node.right = newNode } else { // 递归,继承用新节点和右子节点比较 node.right = this._insert(node.right, newNode) // 重新检查并调整当前这个节点的平衡 node = this.keepBalance(node) } } return node } // 判断树中是否包含某一个数 has(data) { // 从根节点开始查找 return this._has(this.root, data) } _has(node, data) { // 如果节点为空,说明树中没有这个数 if (node == null) { return false } // 如果值和前节点值相同,找到了 if (data == node.data) { return true } else if (data < node.data) { // 到左子树中查找 return this._has(node.left, data) } else if (data > node.data) { // 到右子树中查找 return this._has(node.right, data) } } // 前序遍历(根左右) preOrder() { this._preOrder(this.root) } // 开始的节点 _preOrder(node) { if (node === null) return // 根 console.log(node.data) // 左 this._preOrder(node.left) // 右 this._preOrder(node.right) } // 中序遍历(左根右) midOrder() { this._midOrder(this.root) } // 开始的节点 _midOrder(node) { if (node === null) return // 左 this._midOrder(node.left) // 根 console.log(node.data) // 右 this._midOrder(node.right) } // 后序遍历(左右根) postOrder() { this._postOrder(this.root) } // 开始的节点 _postOrder(node) { if (node === null) return // 左 this._postOrder(node.left) // 右 this._postOrder(node.right) // 根 console.log(node.data) } // 层序遍历 layerOrder() { // 1. 创建一个队列(先入先出) let queue = [] // 2. 先根节点放到队尾 queue.push(this.root) // 3. 循环队列 let node while (queue.length > 0) { // 4. 从队头取出节点 node = queue.shift() // 5. 左右子节点放入队尾 if (node.left !== null) queue.push(node.left) if (node.right !== null) queue.push(node.right) // 6. 输出当前节点(使用这个节点) console.log(node.data) } } // 获取节点高度 getHeight(node) { if (node === null) return 0 // 左右节点中最长的边数 return Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1 } // 左旋转 rotateToL(node) { // 1. 取出右子节点(C) let tmp = node.right // 2. 父节点指向右子节点的左子节点 node.right = tmp.left // 3. 右子节点的左子节点指向父节点 tmp.left = node // 4. 返回新的顶点 return tmp } // 右旋转 rotateToR(node) { let tmp = node.left node.left = tmp.right tmp.right = node return tmp } // 先左旋再右旋转 rorateToLR(node) { // 让左子节点左旋转,并让旋转之后的顶点做为左子节点 node.left = this.rotateToL(node.left) // 再让当前节点右旋转 return this.rotateToR(node) } // 先右旋转再左旋转 rotateToRL(node) { // 让右子节点先右旋转,并用新的顶点做为右子节点 node.right = this.rotateToR(node.right) // 当前节点左旋 return this.rotateToL(node) } // 调整二叉树为平衡状态 keepBalance(node) { if(node === null) return node // 1. 计算左右子节点的高度 let leftHeight = this.getHeight(node.left) let rightHeight = this.getHeight(node.right) // 2. 判断是否不平衡,并判断是 L 型还是 R 型 if (leftHeight - rightHeight > 1) { // L 型 // 3. 判断左子节点的左右谁高 if (this.getHeight(node.left.left) >= this.getHeight(node.left.right)) { // L 型 // LL型-》右旋转 node = this.rotateToR(node) } else { // R 型 // LR开进:先左旋转,再右旋转 node = this.rorateToLR(node) } } else if (rightHeight - leftHeight > 1) { // R 型 // 4. 判断右子节点的左右谁高 if (this.getHeight(node.right.left) >= this.getHeight(node.right.right)) { // L // RL型 -》先右旋转再左旋转 node = this.rotateToRL(node) } else { // RR型-》左旋转 node = this.rotateToL(node) } } return node } // 删除数据 deleteNode(data) { // 从根开始比较删除 this.root = this._deleteNode(this.root, data) } _deleteNode(node, data) { // 没有子节点了,不用删除 if (node == null) return null if (data < node.data) { // 往左走 node.left = this._deleteNode(node.left, data) // 重新调整节点的平衡 return this.keepBalance(node) } else if (data > node.data) { // 往右走 node.right = this._deleteNode(node.right, data) // 重新调整节点 平衡 return this.keepBalance(node) } else { // 如果相等就是找到了节点位置 ,要删除当前节点 // 情况一、有两个子节点 if (node.left !== null && node.right !== null) { // 取出右子节点 let tmp = node.right // 找出最小的节点(最左边的分支) while (tmp.left !== null) { tmp = tmp.left } // 把最小的值赋给当前节点 node.data = tmp.data // 从右分支中删除这个最小的节点 node.right = this._deleteNode(this.right, tmp.data) // 重新调整平衡 node = this.keepBalance(node) return node } else { // 情况二、没有子节点 if (node.left === null && node.right === null) { node = null return null } // 情况三、只有左节点 if (node.left !== null) { // 左节点替换当前节点 node = node.left return node } // 情况四、只有右节点 if (node.right !== null) { // 右节点替换当前节点 node = node.right return node } } } } } const avl = new AVLTree() avl.insert(3) avl.insert(2) avl.insert(1) avl.insert(4) avl.insert(5) avl.deleteNode(1) console.log(avl.root) ~~~