赞
踩
平衡树是一种数据结构,它能够保持在插入或删除操作后,树的高度保持较小的增量,从而保持树的平衡性,提高了查找、插入和删除操作的效率。AVL树、红黑树、B树以及B+树都是常见的平衡树的变种,它们在不同的应用场景中具有不同的优势和特点。
AVL 树在需要严格平衡的场景下使用,例如需要快速的插入、删除和查找操作,而对内存消耗要求较高的情况。
红黑树是一种近似平衡的二叉搜索树,它在插入和删除操作上比 AVL 树更加高效,常用于各种编程语言的标准库中,例如 C++ 的 std::map
和 Java 的 TreeMap
。
B 树和 B+ 树广泛应用于数据库索引和文件系统中,它们能够有效地支持范围查询和顺序访问,并且能够处理大量数据。
AVL树是一种严格平衡的二叉搜索树,它要求任何节点的左子树和右子树的高度最多相差1。为了维持平衡,AVL树在插入或删除操作后会通过旋转操作来调整树的结构,以保持平衡性。AVL树的查询、插入和删除操作的时间复杂度都是O(log n)。
- class AVLNode {
- /**
- * 构造函数,用于创建一棵二叉树节点
- *
- * @param key 节点的键值
- */
- constructor(key) {
- this.key = key;
- this.left = null;
- this.right = null;
- this.height = 1;
- }
- }
-
- class AVLTree {
- /**
- * 构造函数
- *
- * @returns 无返回值
- */
- constructor() {
- this.root = null;
- }
-
- /**
- * 获取节点高度
- *
- * @param node 节点对象
- * @returns 返回节点高度,如果节点不存在则返回0
- */
- getHeight(node) {
- // 如果节点存在,则返回节点的高度,否则返回0
- return node ? node.height : 0;
- }
-
- /**
- * 获取节点的平衡因子
- *
- * @param node 节点对象
- * @returns 返回节点的平衡因子,若节点为空则返回0
- */
- getBalanceFactor(node) {
- // 如果节点存在,则返回节点的高度,否则返回0
- return node ? this.getHeight(node.left) - this.getHeight(node.right) : 0;
- }
-
- /**
- * 更新节点高度
- *
- * @param node 节点对象
- */
- updateHeight(node) {
- // 更新节点的高度为左子树和右子树中较高的高度加1
- node.height = Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1;
- }
-
- /**
- * 将节点y进行右旋操作
- *
- * @param y 要进行右旋操作的节点
- * @returns 返回旋转后的新根节点
- */
- rotateRight(y) {
- // 将y的左子节点赋值给x
- let x = y.left;
- // 将x的右子节点赋值给T2
- let T2 = x.right;
-
- // 将y赋值给x的右子节点
- x.right = y;
- // 将T2赋值给y的左子节点
- y.left = T2;
-
- // 更新y的高度
- this.updateHeight(y);
- // 更新x的高度
- this.updateHeight(x);
-
- // 返回x
- return x;
- }
-
- /**
- * 左旋操作
- *
- * @param x 待旋转的节点
- * @returns 返回旋转后的新根节点
- */
- rotateLeft(x) {
- // 将节点x的右子节点赋值给y
- let y = x.right;
- // 将节点y的左子节点赋值给T2
- let T2 = y.left;
-
- // 将节点x赋值给节点y的左子节点
- y.left = x;
- // 将T2赋值给节点x的右子节点
- x.right = T2;
-
- // 更新节点x的高度
- this.updateHeight(x);
- // 更新节点y的高度
- this.updateHeight(y);
-
- // 返回旋转后的新根节点y
- return y;
- }
-
- /**
- * 向AVL树中插入节点
- *
- * @param node 当前节点
- * @param key 待插入节点的键值
- * @returns 返回插入后的节点
- */
- insert(node, key) {
- // 如果节点为空,则创建一个新的节点并返回
- if (!node) {
- return new AVLNode(key);
- }
-
- // 如果插入的键小于节点的键,则在左子树中插入
- if (key < node.key) {
- node.left = this.insert(node.left, key);
- }
- // 如果插入的键大于节点的键,则在右子树中插入
- else if (key > node.key) {
- node.right = this.insert(node.right, key);
- }
- // 如果插入的键等于节点的键,则直接返回该节点
- else {
- return node;
- }
-
- // 更新节点的高度
- this.updateHeight(node);
-
- // 获取节点的平衡因子
- let balance = this.getBalanceFactor(node);
-
- // 左左情况:左子树高度大于右子树高度2及以上,且插入的键小于左子树的最小键
- // 右旋
- // 左左情况
- if (balance > 1 && key < node.left.key) {
- return this.rotateRight(node);
- }
-
- // 右右情况:右子树高度大于左子树高度2及以上,且插入的键大于右子树的最大键
- // 左旋
- // 右右情况
- if (balance < -1 && key > node.right.key) {
- return this.rotateLeft(node);
- }
-
- // 左右情况:左子树高度大于右子树高度2及以上,且插入的键大于左子树的最小键
- // 先左旋,再右旋
- // 左右情况
- if (balance > 1 && key > node.left.key) {
- node.left = this.rotateLeft(node.left);
- return this.rotateRight(node);
- }
-
- // 右左情况:右子树高度大于左子树高度2及以上,且插入的键小于右子树的最大键
- // 先右旋,再左旋
- // 右左情况
- if (balance < -1 && key < node.right.key) {
- node.right = this.rotateRight(node.right);
- return this.rotateLeft(node);
- }
-
- // 返回插入后的节点
- return node;
- }
- }
-
- // 示例
- const avlTree = new AVLTree();
- avlTree.root = avlTree.insert(avlTree.root, 10);
- avlTree.root = avlTree.insert(avlTree.root, 20);
- avlTree.root = avlTree.insert(avlTree.root, 30);
- console.log(avlTree.root); // AVLNode { key: 20, left: AVLNode { key: 10, ... }, right: AVLNode { key: 30, ... }, ... }
红黑树是一种近似平衡的二叉搜索树,它通过一些附加的约束条件来保持树的大致平衡。红黑树的节点可以是红色或黑色,它要求在插入或删除操作后,树的黑色高度相同。红黑树通过颜色变换和旋转操作来调整树的结构,以满足约束条件。红黑树相对于AVL树来说,旋转操作更少,插入和删除操作更快,但是查询操作略慢一些。
- const RED = 'red';
- const BLACK = 'black';
-
- class RedBlackNode {
- /**
- * 构造函数,用于创建一个节点对象
- *
- * @param key 节点的键值
- * @param color 节点的颜色
- */
- constructor(key, color) {
- this.key = key; // 设置当前节点的键值
- this.left = null; // 初始化左子节点为 null
- this.right = null;// 初始化右子节点为 null
- this.parent = null; // 初始化父节点为 null
- this.color = color;// 设置当前节点的颜色
- }
- }
-
- class RedBlackTree {
- /**
- * 构造函数
- */
- constructor() {
- // 初始化根节点为null
- this.root = null;
- }
-
- /**
- * 将二叉树节点左旋转
- *
- * @param node 需要左旋转的节点
- */
- rotateLeft(node) {
- // 获取当前节点的右子节点
- let rightChild = node.right;
- // 将当前节点的右子节点的左子节点赋值给当前节点的右子节点
- node.right = rightChild.left;
- // 如果当前节点的右子节点的左子节点不为空
- if (rightChild.left !== null) {
- // 将当前节点的右子节点的左子节点的父节点指向当前节点
- rightChild.left.parent = node;
- }
-
- // 将当前节点的右子节点的父节点指向当前节点的父节点
- rightChild.parent = node.parent;
-
- // 如果当前节点的父节点为空
- if (node.parent === null) {
- // 将当前节点的右子节点设置为根节点
- this.root = rightChild;
- // 如果当前节点是其父节点的左子节点
- } else if (node === node.parent.left) {
- // 将当前节点的父节点的左子节点设置为当前节点的右子节点
- node.parent.left = rightChild;
- // 如果当前节点是其父节点的右子节点
- } else {
- // 将当前节点的父节点的右子节点设置为当前节点的右子节点
- node.parent.right = rightChild;
- }
-
- // 将当前节点的右子节点的左子节点设置为当前节点
- rightChild.left = node;
- // 将当前节点的父节点设置为当前节点的右子节点
- node.parent = rightChild;
- }
-
- /**
- * 将节点右旋
- *
- * @param node 要旋转的节点
- */
- rotateRight(node) {
- // 获取当前节点的左子节点
- let leftChild = node.left;
- // 将当前节点的左子节点指向左子节点的右子节点
- node.left = leftChild.right;
- // 如果左子节点的右子节点不为空
- if (leftChild.right !== null) {
- // 将左子节点的右子节点的父节点指向当前节点
- leftChild.right.parent = node;
- }
- // 将左子节点的父节点指向当前节点的父节点
- leftChild.parent = node.parent;
- // 如果当前节点的父节点为空
- if (node.parent === null) {
- // 将根节点指向左子节点
- this.root = leftChild;
- // 如果当前节点是父节点的右子节点
- } else if (node === node.parent.right) {
- // 将父节点的右子节点指向左子节点
- node.parent.right = leftChild;
- // 否则
- } else {
- // 将父节点的左子节点指向左子节点
- node.parent.left = leftChild;
- }
- // 将左子节点的右子节点指向当前节点
- leftChild.right = node;
- // 将当前节点的父节点指向左子节点
- node.parent = leftChild;
- }
-
- /**
- * 插入节点
- *
- * @param key 节点值
- */
- insert(key) {
- // 创建一个新的红黑树节点,颜色为红色
- let newNode = new RedBlackNode(key, RED);
-
- // 将新节点插入到红黑树中,并返回更新后的根节点
- this.root = this.insertNode(this.root, newNode);
-
- // 修复红黑树的性质
- this.fixTreeProperties(newNode);
- }
-
- /**
- * 在二叉搜索树中插入节点
- *
- * @param root 二叉搜索树的根节点
- * @param node 要插入的节点
- * @returns 返回插入节点后的二叉搜索树的根节点
- */
- insertNode(root, node) {
- // 如果根节点为空,直接返回新节点作为根节点
- if (root === null) {
- return node;
- }
- // 如果新节点的键值小于根节点的键值,则递归地将新节点插入到左子树中
- if (node.key < root.key) {
- root.left = this.insertNode(root.left, node);
- // 将新节点的父节点设置为当前根节点
- root.left.parent = root;
- // 如果新节点的键值大于根节点的键值,则递归地将新节点插入到右子树中
- } else if (node.key > root.key) {
- root.right = this.insertNode(root.right, node);
- // 将新节点的父节点设置为当前根节点
- root.right.parent = root;
- }
-
- // 返回根节点
- return root;
- }
-
- /**
- * 修复树属性
- *
- * @param node 当前节点
- */
- fixTreeProperties(node) {
- // 当当前节点不是根节点且其父节点颜色为红色时,执行循环
- while (node !== this.root && node.parent.color === RED) {
- // 获取当前节点的父节点和祖父节点
- let parentNode = node.parent;
- let grandParentNode = node.parent.parent;
-
- // 如果父节点是祖父节点的左子节点
- if (parentNode === grandParentNode.left) {
- // 获取叔叔节点
- let uncleNode = grandParentNode.right;
-
- // 如果叔叔节点不为空且颜色为红色
- if (uncleNode !== null && uncleNode.color === RED) {
- // 将祖父节点颜色设为红色
- grandParentNode.color = RED;
- // 将父节点颜色设为黑色
- parentNode.color = BLACK;
- // 将叔叔节点颜色设为黑色
- uncleNode.color = BLACK;
- // 将当前节点更新为祖父节点,继续下一轮循环
- node = grandParentNode;
- } else {
- // 如果当前节点是父节点的右子节点
- if (node === parentNode.right) {
- // 执行左旋操作
- this.rotateLeft(parentNode);
- // 将当前节点更新为父节点
- node = parentNode;
- // 更新父节点为当前节点的父节点
- parentNode = node.parent;
- }
- // 执行右旋操作
- this.rotateRight(grandParentNode);
- // 交换父节点和祖父节点的颜色
- [parentNode.color, grandParentNode.color] = [grandParentNode.color, parentNode.color];
- // 将当前节点更新为父节点,继续下一轮循环
- node = parentNode;
- }
- } else {
- // 如果父节点是祖父节点的右子节点
- // 获取叔叔节点
- let uncleNode = grandParentNode.left;
-
- // 如果叔叔节点不为空且颜色为红色
- if (uncleNode !== null && uncleNode.color === RED) {
- // 将祖父节点颜色设为红色
- grandParentNode.color = RED;
- // 将父节点颜色设为黑色
- parentNode.color = BLACK;
- // 将叔叔节点颜色设为黑色
- uncleNode.color = BLACK;
- // 将当前节点更新为祖父节点,继续下一轮循环
- node = grandParentNode;
- } else {
- // 如果当前节点是父节点的左子节点
- if (node === parentNode.left) {
- // 执行右旋操作
- this.rotateRight(parentNode);
- // 将当前节点更新为父节点
- node = parentNode;
- // 更新父节点为当前节点的父节点
- parentNode = node.parent;
- }
- // 执行左旋操作
- this.rotateLeft(grandParentNode);
- // 交换父节点和祖父节点的颜色
- [parentNode.color, grandParentNode.color] = [grandParentNode.color, parentNode.color];
- // 将当前节点更新为父节点,继续下一轮循环
- node = parentNode;
- }
- }
- }
- // 将根节点颜色设为黑色
- this.root.color = BLACK;
- }
- }
-
- // 示例
- const rbTree = new RedBlackTree();
- rbTree.insert(10);
- rbTree.insert(20);
- rbTree.insert(30);
- console.log(rbTree.root); // RedBlackNode { key: 20, color: 'black', ... }
B树是一种多路搜索树,它的每个节点可以存储多个关键字,并且可以有多个子节点。B树的节点拥有较大的容量,使得每个节点的关键字数量相对较少,这样可以降低树的高度,提高查询效率。B树常用于文件系统和数据库中,用于索引大量数据的情况。
- class BTreeNode {
- /**
- * 构造函数
- *
- * @param leaf 是否为叶子节点,默认为false
- */
- constructor(leaf = false) {
- this.keys = [];
- this.children = [];
- this.leaf = leaf;
- }
- }
-
- class BTree {
- /**
- * 构造函数
- *
- * @param degree 树的度数
- */
- constructor(degree) {
- this.root = null;
- this.degree = degree;
- }
-
- /**
- * 搜索指定节点
- *
- * @param key 要搜索的节点值
- * @returns 返回与key值相等的节点
- */
- search(key) {
- // 调用 searchNode 方法,传入根节点和关键字进行搜索
- return this.searchNode(this.root, key);
- }
-
- /**
- * 在 B-tree 节点中查找给定的键值,并返回该键值所在的节点
- *
- * @param node B-tree 节点
- * @param key 要查找的键值
- * @returns 返回找到的节点,如果未找到则返回 null
- */
- searchNode(node, key) {
- let i = 0;
-
- // 遍历节点的 keys 数组,查找 key 所在的索引位置
- while (i < node.keys.length && key > node.keys[i]) {
- i++;
- }
-
- // 如果找到了 key,并且 key 等于当前索引位置的元素,则返回当前节点
- if (i < node.keys.length && key === node.keys[i]) {
- return node;
- // 如果当前节点是叶子节点,则返回 null
- } else if (node.leaf) {
- return null;
- // 否则,在子节点中继续搜索 key
- } else {
- return this.searchNode(node.children[i], key);
- }
- }
-
- /**
- * 向B树中插入一个键值
- *
- * @param key 要插入的键值
- */
- insert(key) {
- // 如果根节点不存在
- if (!this.root) {
- // 创建一个新的B树节点作为根节点,并且标记为叶子节点
- this.root = new BTreeNode(true);
- // 将传入的键值添加到根节点的键值数组中
- this.root.keys.push(key);
- } else {
- // 如果根节点的键值数组已满(达到2倍的度数减1)
- if (this.root.keys.length === (2 * this.degree) - 1) {
- // 创建一个新的B树节点作为新的根节点,并且标记为非叶子节点
- let newRoot = new BTreeNode(false);
- // 将原来的根节点添加到新根节点的子节点数组中
- newRoot.children.push(this.root);
- // 调用splitChild方法拆分子节点
- this.splitChild(newRoot, 0, this.root);
- // 更新根节点为新创建的节点
- this.root = newRoot;
- }
- // 在非满的节点中插入键值
- this.insertNonFull(this.root, key);
- }
- }
-
- /**
- * 在非满节点中插入键值对
- *
- * @param node 节点对象
- * @param key 待插入的键
- */
- insertNonFull(node, key) {
- // 获取节点中键的最后一个索引
- let i = node.keys.length - 1;
-
- // 如果节点是叶子节点
- if (node.leaf) {
- // 当索引大于等于0且key小于当前索引对应的键时,索引减1
- while (i >= 0 && key < node.keys[i]) {
- i--;
- }
- // 在索引i+1的位置插入key
- node.keys.splice(i + 1, 0, key);
- } else {
- // 当索引大于等于0且key小于当前索引对应的键时,索引减1
- while (i >= 0 && key < node.keys[i]) {
- i--;
- }
- // 索引加1
- i++;
- // 如果当前索引对应的子节点的键的数量等于(2 * this.degree) - 1
- if (node.children[i].keys.length === (2 * this.degree) - 1) {
- // 调用splitChild方法拆分子节点
- this.splitChild(node, i, node.children[i]);
- // 如果key大于当前索引对应的键
- if (key > node.keys[i]) {
- // 索引加1
- i++;
- }
- }
- // 递归调用insertNonFull方法,将key插入到当前索引对应的子节点中
- this.insertNonFull(node.children[i], key);
- }
- }
-
- /**
- * 将子节点拆分为两个节点,并将拆分后的新节点插入到父节点中
- *
- * @param parent 父节点
- * @param index 父节点中子节点的索引
- * @param child 要拆分的子节点
- */
- splitChild(parent, index, child) {
- // 创建一个新的B树节点,叶子节点状态与child相同
- let newChild = new BTreeNode(child.leaf);
- // 计算中间索引位置
- let midIndex = Math.floor(this.degree - 1);
-
- // 将child节点的keys数组从中间索引位置后的部分赋值给newChild的keys数组
- newChild.keys = child.keys.splice(midIndex + 1);
- // 如果child节点不是叶子节点
- if (!child.leaf) {
- // 将child节点的children数组从中间索引位置后的部分赋值给newChild的children数组
- newChild.children = child.children.splice(midIndex + 1);
- }
-
- // 将child节点的keys数组中间索引位置的元素插入到parent节点的keys数组的指定位置
- parent.keys.splice(index, 0, child.keys[midIndex]);
- // 将newChild节点插入到parent节点的children数组的指定位置
- parent.children.splice(index + 1, 0, newChild);
- }
- }
-
- // 示例
- const bTree = new BTree(2);
- bTree.insert(10);
- bTree.insert(20);
- bTree.insert(5);
- console.log(bTree.search(10)); // BTreeNode { keys: [ 10 ], children: [], leaf: true }
- console.log(bTree.search(15)); // null
B+树是B树的一种变体,它与B树的区别在于B+树的非叶子节点不存储关键字的值,只存储关键字和指向子节点的指针。所有的叶子节点按顺序连接起来,形成一个有序链表,方便范围查询。B+树适用于数据库索引等需要范围查询的场景。
- class BPlusTreeNode {
- /**
- * 构造函数
- */
- constructor() {
- this.keys = [];
- this.children = [];
- }
- }
-
- class BPlusTree {
- /**
- * 构造函数
- *
- * @param degree 树的度数
- */
- constructor(degree) {
- this.root = null;
- this.degree = degree;
- }
-
- /**
- * 搜索指定关键字
- *
- * @param key 关键字
- * @returns 返回搜索到的节点,若未找到则返回null
- */
- search(key) {
- // 调用 searchNode 方法进行搜索
- return this.searchNode(this.root, key);
- }
-
- /**
- * 在树中搜索节点
- *
- * @param node 当前节点
- * @param key 待搜索的键
- * @returns 返回找到的节点,若未找到则返回null
- */
- searchNode(node, key) {
- // 如果节点为空,则返回null
- if (!node) return null;
- // 初始化索引i为0
- let i = 0;
-
- // 循环遍历节点的keys数组,直到找到key应该插入的位置
- while (i < node.keys.length && key >= node.keys[i]) {
- i++;
- }
- // 如果节点没有子节点,则返回当前节点
- if (node.children.length === 0) {
- return node;
- }
- // 递归调用searchNode方法,在子节点中继续搜索
- return this.searchNode(node.children[i], key);
- }
-
- /**
- * 向B+树中插入键值
- *
- * @param key 键值
- */
- insert(key) {
- // 如果根节点不存在
- if (!this.root) {
- // 创建一个新的根节点
- this.root = new BPlusTreeNode();
- // 将键值添加到根节点的键值数组中
- this.root.keys.push(key);
- } else {
- // 如果根节点的键值数组已满
- if (this.root.keys.length === (2 * this.degree) - 1) {
- // 创建一个新的根节点
- let newRoot = new BPlusTreeNode();
- // 将原根节点添加到新根节点的子节点数组中
- newRoot.children.push(this.root);
- // 调用 splitChild 方法进行节点分裂
- this.splitChild(newRoot, 0, this.root);
- // 更新根节点为新创建的根节点
- this.root = newRoot;
- }
- // 调用 insertNonFull 方法将键值插入到非满的节点中
- this.insertNonFull(this.root, key);
- }
- }
-
- /**
- * 在非满节点中插入一个键值对
- *
- * @param node 当前节点
- * @param key 待插入的键
- */
- insertNonFull(node, key) {
- // 获取节点最后一个键的索引
- let i = node.keys.length - 1;
- // 如果节点没有子节点
- if (node.children.length === 0) {
- // 当索引大于等于0且key小于当前索引对应的键时,索引减1
- while (i >= 0 && key < node.keys[i]) {
- i--;
- }
- // 在索引i+1的位置插入key
- node.keys.splice(i + 1, 0, key);
- } else {
- // 当索引大于等于0且key小于当前索引对应的键时,索引减1
- while (i >= 0 && key < node.keys[i]) {
- i--;
- }
- // 索引加1,指向当前key应该插入的位置
- i++;
- // 如果当前子节点的键数量等于最大度减1
- if (node.children[i].keys.length === (2 * this.degree) - 1) {
- // 对当前子节点进行分裂
- this.splitChild(node, i, node.children[i]);
- // 如果key大于当前索引对应的键
- if (key > node.keys[i]) {
- // 索引加1,指向分裂后应该插入的位置
- i++;
- }
- }
- // 在指定子节点中递归插入key
- this.insertNonFull(node.children[i], key);
- }
- }
-
- /**
- * 将子节点从父节点中拆分,生成新的子节点并插入到父节点的子节点数组中
- *
- * @param parent 父节点
- * @param index 父节点中子节点的索引
- * @param child 要拆分的子节点
- */
- splitChild(parent, index, child) {
- // 创建一个新的B+树节点
- let newChild = new BPlusTreeNode();
-
- // 计算中间索引
- let midIndex = Math.floor(this.degree - 1);
-
- // 将child节点的keys数组从midIndex+1处开始的部分赋值给newChild节点的keys
- newChild.keys = child.keys.splice(midIndex + 1);
-
- // 如果child节点有子节点
- if (child.children.length !== 0) {
- // 将child节点的children数组从midIndex+1处开始的部分赋值给newChild节点的children
- newChild.children = child.children.splice(midIndex + 1);
- }
-
- // 将child节点的keys数组中midIndex位置的元素插入到parent节点的keys数组的index位置
- parent.keys.splice(index, 0, child.keys[midIndex]);
-
- // 将newChild节点插入到parent节点的children数组的index+1位置
- parent.children.splice(index + 1, 0, newChild);
- }
- }
-
- // 示例
- const bPlusTree = new BPlusTree(2);
- bPlusTree.insert(10);
- bPlusTree.insert(20);
- bPlusTree.insert(5);
- console.log(bPlusTree.search(10)); // BPlusTreeNode { keys: [ 10 ], children: [] }
- console.log(bPlusTree.search(15)); // BPlusTreeNode { keys: [ 10 ], children: [] }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。