C++ 替罪羊树2025年03月22日 | 阅读 9 分钟 牺牲树(Scapegoat Trees) 是一种自平衡二叉搜索树,通过在树失衡时重建子树来保持其操作(如插入、删除和搜索)的效率。与 AVL 树或红黑树在每次插入或删除后立即使用旋转来维持平衡不同,牺牲树采用了一种不那么激进的平衡策略,它允许树在一段时间内保持失衡。 这意味着当树变得过于失衡时,树不会立即重新平衡,而是允许失衡增长。一旦失衡超出了树深处的某个阈值,就会重建一个子树。通过使用 α-平衡准则(用于检测失衡的阈值),树可以确保其高度保持对数级别。这有助于在处理动态和不可预测的数据集时保持高效的搜索、插入和删除操作。 关键特性C++ 中牺牲树 的几个关键特性如下:
关键操作C++ 中牺牲树 的几个关键操作如下: 1. 插入节点最初可以像在普通 BST 中一样插入到牺牲树中。通过与现有节点比较,将节点放置在其正确的位置。插入后,树会检查是否有节点刚刚变得失衡,方法是将新插入节点的深度与应有的深度(与 log(n) 成正比,其中 (n) 是节点数)进行比较。如果实际深度大于此阈值,树将找到最深的失衡节点(牺牲节点),并重建以该节点为根的子树以恢复平衡。 2. 删除牺牲树中的删除操作类似于普通 BST 的删除操作。将被删除的节点被移除,树被重构以保持二叉搜索树 (BST) 的属性。然而,与插入不同,删除不会立即触发子树重建。相反,只有当删除后树中的节点数量下降到由树达到的最大大小的某个比例决定的阈值以下时,树才会重建。这使得即使在多次删除后,高度也能保持在对数范围内。 3. 查找牺牲节点牺牲节点是检测到失衡的节点。插入后,如果其深度超过对数边界,树将从新插入的节点向根回溯,并检查每个节点的子树大小。第一个违反 α-平衡条件的节点被称为牺牲节点。该牺牲节点将成为将被重建的子树的根。 4. 重建一旦找到牺牲节点,该节点处的子树将被重建为一个最优平衡的子树。通常,这种重建是通过收集子树中的所有节点,排序,然后构建一个新的平衡树来完成的。这种重建确保了整个树的高度最多与 log(n) 成正比;因此,由于一些失衡的节点,不会发生显著的性能下降。 示例让我们通过一个例子来说明 C++ 中的牺牲树。 输出 Tree after insertions: 30 40 50 60 70 Searching for 30: Found Searching for 100: Not Found Tree after deleting 30: 40 50 60 70 说明此 C++ 牺牲树实现是一种自平衡二叉搜索树 (BST),它通过偶尔重建失衡的子树来保持平衡。代码包含关键组件,如节点结构、插入、搜索、删除和子树重建。 节点结构 Node struct 代表每个树节点,它包含一个键 (key)、指向左右子节点的指针 (left 和 right) 以及一个整数 size,表示以该节点为根的子树中的节点数。size 字段至关重要,因为它有助于检查子树的平衡。使用此 α-平衡准则来检测平衡并重建失衡的子树,以确保其树的高度保持对数范围。因此,即使处理动态和非确定性数据集,搜索、插入和删除通常也是高效的。 树结构 主类 ScapegoatTree 包含根节点 (root)、树达到的最大大小 (maxSize) 和平衡因子 (alpha),通常设置为 2/3。平衡因子控制何时将子树视为失衡。在空树中,根最初为 nullptr,maxSize 初始化为零。 插入操作 insert() 函数首先执行典型的 BST 插入。插入新节点后,树会使用 isBalanced() 函数检查从新插入节点到根的路径上的任何节点是否已失衡。如果其左子树或右子树的大小大于其总大小的某个分数(由 alpha 控制),则该节点失衡。 如果找到失衡,将识别最深的失衡节点(牺牲节点)。插入操作保证了 O(logn) 的摊销时间复杂度,因为重建只在失衡发生时偶尔发生。 搜索操作 search() 函数很简单。它像常规 BST 一样遍历树以查找包含所需键的节点。如果找到键,则返回该节点;否则,返回 nullptr。 删除操作 remove() 函数遵循标准的 BST 删除规则,其中通过用其中序前驱或后继(如果它有两个子节点)替换节点,或者通过删除节点并重新连接其子节点来移除节点。与插入不同,删除不会立即触发重建。相反,会检查树的大小,如果它相对于最大大小 (小于 alpha * maxSize) 变得太小,则会重建整个树以确保平衡。 子树重建 子树重建过程在维持牺牲树的平衡方面起着至关重要的作用。它涉及收集子树中的所有节点(使用 flatten() 函数),并将它们重建为一棵完美平衡的树。它确保失衡的子树能够以对树的其余部分最小的干扰得到纠正。 复杂度分析时间复杂度插入
删除
搜索
空间复杂度
下一主题C++ 中的连续树 |
七段显示器是一种电子显示设备,它使用七个独立的段来显示数字和一些字母字符。每个段都用字母 a 到 g 标记。液晶显示器、计算器和电子测量设备通常使用七段显示器...
阅读9分钟
识别凸对象之间碰撞的一种流行方法是 Gilbert-Johnson-Keerthi (GJK) 算法。由于其有效性和多维性,它在计算机图形学、物理模拟和游戏开发中非常有用。此过程的目的是确定两个凸对象是否相交或……
阅读9分钟
在本文中,我们将通过几个例子进行讨论。Srinivasa Ramanujan 提出的 Ramanujan-Nagell 猜想,并由 Trygve Nagell 扩展,指出方程 2n-7 = x2 在自然数 n 和 x 中有解,当且仅当 n 的值为 3,...
阅读 4 分钟
“连接木棍的最小成本”问题是一个常见的算法任务,其中必须将多个木棍元素合并成一根,成本等于连接的两个木棍长度之和。目标是降低总体成本... ...
11 分钟阅读
在数字王国中,特殊的性质和独特的模式在数学领域广阔无垠,有些想法因其稀缺性而显得特别。令人兴奋的是,发现所谓的 Magnanimous Numbers 是其中引人入胜的想法之一。Magnanimous Number……
阅读 10 分钟
C++ 中的 Lambda 函数提供了一种简洁的方式来定义微小的私有函数。默认情况下,来自其周围作用域的变量可以通过值或引用被 lambda 函数捕获。但是,如果没有 mutable 关键字,捕获的变量就不能被更改。Lambda...
阅读 4 分钟
在 C++ 中,标点符号不定义产生值的操作,而是为编译器提供语法和语义含义。某些标点符号在单独使用或组合使用时也可能对预处理器或 C++ 运算符很重要。基本 C++ 标点符号如下。分号...
阅读 4 分钟
在本文中,我们将通过不同的方法讨论它。在讨论其方法之前,我们必须先了解 C++ 中的 Nicomachus 定理。用一个例子解释 Nicomachus 定理 k 的平方等于从 1 到 k 的奇数的和……
阅读 17 分钟
自传数(n)是指定基数中的一个 b 位整数。在该数中,位置 p(其中最高有效位是位置 0,最低有效位是位置 (b−1))处的每个数字反映了该数字出现的次数...
5 分钟阅读
引言 在数论中,皮尔庞特素数(Pierpont primes)备受关注。以 James Pierpont 的名字命名的这些素数形式为 2^u ⋅ 3^v +1,其中 u ≥ 0 且 v ≥ 0。称这些素数为不可逆素数是常见且完全可以接受的。它们是...
阅读 8 分钟
我们请求您订阅我们的新闻通讯以获取最新更新。
我们提供所有技术(如 Java 教程、Android、Java 框架)的教程和面试问题
G-13, 2nd Floor, Sec-3, Noida, UP, 201301, India