AA树—简单的红黑树

AA树是一种平衡二叉搜索树,通过skew和split操作保持平衡。skew用于修正左向水平链,split用于处理连续水平链。插入操作采用递归,新节点level为1,而删除操作不使用递归。虽然AA树旋转次数多于红黑树,但其时间复杂度仍为O(logn)。
        AA树是Arne Andersson教授在他的论文"Balanced search trees made simple"中介绍的一个红黑树变种,设计的目的是减少RB树考虑的cases。AA树是一颗红黑树,但是规定红色结点不能作为任何结点的左孩子,也就是说红色结点只能作为右孩子。红黑树和 2-3-4树是等价的数据结构,那么只能有右红孩子结点的红黑树本质上就是 2-3树,所以AA-树的操作结果可以用对应的2-3树上操作的结果验证正确性。另外AA树为实现方便,不再使用红黑两种颜色,而是用level标记结点。level实际上就相当于RB树中的black height,叶子结点的level等于1(反过来,level等于1的不一定是叶子结点,因为等于1的结点可能有一个红色的右孩子),红色结点使用它的父结点的level,黑色结点比它的父结点的level小1。另外,下面两种情况是禁止出现的:

1)连续两个水平方向链(horizontal link),所谓horizontal link是指一个结点跟它的右孩子结点的level相同(左孩子结点永远比它的父结点level小1)。这个规定其实相当于RB树中不能出现两个连续的红色结点。

2)向左的水平方向链(left horizontal link),也就是说一个结点最多只能出现一次向右的水平方向链。这是因为left horizontal link相当于左孩子能为红色结点,这在AA树的定义中是不允许的。

        在插入和删除操作中,可能会出现上面两个禁止发生的情况,这时候就需要通过树的旋转操作来纠正。AA树中只有两个基本操作:skew和split。前者用于纠正出现向左的水平方向链,后者用于纠正出现连续两个水平方向链的情况。skew就是一个右旋转,split是一个左旋转,但两者不是互逆的。skew操作之后可能引起1)的发生(当skew之前已经有一个右孩子的level跟当前结点的level相同),这时需要配合使用split操作。split操作的特点是新的子树的根节点level增加1, 从而会在它的父结点中出现1)(当它作为父结点的左孩子)或者在它的父结点中出现2)(当它作为父结点的右孩子而且父结点跟祖父结点的level相同),这时需要通过skew和split操作纠正这两种情况。

 

 


 
由于split引起的新问题发生在parent一级局部结点,而skew引起的新问题只发生在当前局部结点,所以在实现时需要先skew,再split。

在下面的插入操作中使用递归,删除操作没有使用递归。新插入的结点level等于1。

### 自定义AA实现中`Q.erase(it--)`失效的原因分析 #### 1. **后置递减操作符的实现问题** 在自定义AA实现中,`it--`(后置递减操作符)可能未正确处理边界条件或节点关系。例如,若迭代器指向`begin()`时执行`it--`,将导致访问非法内存[^2]。此外,删除操作可能修改了结构,但迭代器未同步更新指针关系,从而引发迭代器失效。 **代码示例**: ```cpp iterator operator--(int) { iterator tmp = *this; if (current_node != nullptr) { current_node = find_predecessor(current_node); // 需实现前驱查找函数 } return tmp; // 返回旧位置的副本 } ``` 若`find_predecessor`未正确实现或未考虑边界情况,可能导致`it--`返回无效迭代器。 #### 2. **删除操作未返回有效迭代器** STL的`map::erase`会返回被删除元素的下一个有效迭代器[^3],而自定义实现若未遵循此逻辑,将直接导致`Q.erase(it--)`后续操作失效。例如: ```cpp // 错误实现:未返回有效迭代器 void erase(iterator it) { delete it.node; // 直接删除节点,未调整结构 // 无返回值,导致外部无法获取下一个有效位置 } ``` #### 3. **AA删除逻辑错误** AA的删除需要严格遵循**平衡规则**。若自定义实现未正确处理以下情况,迭代器将失效: - **双黑节点修正**:未正确处理删除黑色节点导致的“双黑”问题。 - **兄弟节点颜色判断**:旋转逻辑错误会导致结构失衡,破坏迭代器的遍历顺序。 **代码示例**: ```cpp // 简化版AA删除逻辑 Node* erase(Node* pos) { Node* next_node = find_successor(pos); // 查找后继节点 delete_node(pos); // 实现AA删除逻辑 return next_node; // 返回后继节点 } ``` 若`delete_node`未正确调整结构,可能导致迭代器失效。 #### 4. **边界条件未处理** - **删除第一个元素**:若`it`指向`begin()`,执行`it--`会触发未定义行为。 - **删除最后一个元素**:未正确处理`end()`迭代器的回退逻辑。 **代码示例**: ```cpp iterator erase(iterator pos) { if (pos == end()) throw invalid_iterator(); // 检查边界条件 Node* next_node = find_successor(pos.node); // 查找后继节点 delete_node(pos.node); // 删除当前节点 return iterator(next_node); // 返回下一个有效迭代器 } ``` #### 5. **当前实现是否涉及红黑树逻辑** 当前实现并未使用红黑树逻辑,而是基于AA设计。AA红黑树的一种变体,通过限制水平链接数量来简化旋转操作[^4]。因此,尽管两者在删除操作上存在相似性,但具体实现细节不同。 --- ### 示例:修复后的AA删除逻辑 ```cpp // AA删除核心逻辑 Node* delete_node(Node* z) { Node* y = z; Node* x = nullptr; if (z->lson == nullptr) { x = z->rson; transplant(z, z->rson); } else if (z->rson == nullptr) { x = z->lson; transplant(z, z->lson); } else { y = findMin(z->rson); // 找后继节点 x = y->rson; if (y->parent == z) { x->parent = y; } else { transplant(y, y->rson); y->rson = z->rson; y->rson->parent = y; } transplant(z, y); y->lson = z->lson; y->lson->parent = y; } delete z; return x; } ``` --- ### 总结 `Q.erase(it--)`在自定义AA中失效的根本原因包括: 1. **迭代器管理缺陷**:未正确处理删除后的节点链接关系[^2]。 2. **AA删除逻辑错误**:未遵循平衡规则。 3. **操作符重载问题**:后置递减未返回有效的临时副本[^3]。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值