文章目录
红黑树
一致性结构 Persistent Structure
对于数据结构:向量、列表、栈、队列、树、图,他们在经过动态操作后,其逻辑结构会进入一个新的状态,旧的状态将被遗忘,称他们为ehpemeral structure。但我们在实际应用中,有时是需要历史结构的,如果一个数据结构能够支持这种特性,则成为Persistent Structure。
完成这一目标看似不难,我们可以蛮力保存每个版本的数据结构。
单次操作O(logh + logn),累计O(h * n )时间/空间复杂度,这导致空间复杂度随着h呈线性的增长,由于空间复杂度是时间复杂度的下限,这将导致时间复杂度也在增长。
我们尝试将复杂度控制在O(n + h * logn),利用相邻版本之间的关联性。
O(1)重构
为达到上述特性,就BBST树形拓扑结构而言相邻版本的差异不超过O(1),即是前一版本转入后一版本的旋转次数不超过常数次。对于AVL树来说,其插入操作insert(),每次插入进过一次旋转即可重平衡,但删除操作remove()可能会导致由下至上O(logn)次的旋转。
红黑树结构
定义规则
增设外部节点,保证红黑树是真二叉树。
- 1)树根:必为黑色
- 2)外部节点:均未黑色
- 3)其余节点:若为红,则只能有黑孩子,即红结点的的子节点和父节点均为黑
- 4)外部节点到根:途中黑节点数目相等
上图未标识外部节点,外部节点只是方便以后的分析以及实现,并不实际存在。
提升变换
将每一个红色节点提升到与他的黑父亲平齐。
提升后:
在经过提升之后,所有的底层节点呈平齐的分布。而且红节点与其黑父亲等高,于是每棵红黑树都对应于一棵(2,4)-树。
将黑节点与其红孩子视作(关键码合并)超级节点,这无非四种组合,分别对应于4阶B-树的一类内部节点。
接口定义
template<typename T> class RedBlack: public BST<T>
{
public:
//BST::search();
BinNodePosi(T) insert(const T &e);
bool remove(const T &e);
protected:
void solveDpubleRed(BinNodePosi(T) x); //双红修正
void solveDoubleBlack(BinNodePosi(T) x); //双黑修正
int updateHeight(BinNodePosi(T) x); //更新节点高度,特指黑高度
}
template<typename T> int RedBlack<T>:: updateHeight(BinNodePosi(T) x)
{
x->height=max(stature(x->lc), stature(x->rc));
if(IsBlack(x)) x->height++; //只记录黑节点
return x->height;
}
算法实现 插入
拟插入关键码e,按BST的常规插入算法插入,x=insert(e),设x的父亲p=x->parent,并将x染红。
若p->colorx->colorR,发生双红,此时我们考察x的祖父g=p->parent,和x的叔父u=(p==g->lc?g->rc : g->lc),并视u的颜色处理。
template<typename T> RedBlack<T>:: insert(const T &e)
{
BinNodePosi(T) &x=search(e);
if(x)
return x;
x=new BinNode<T>(e, _hot, NULL, NULL, -1);
_size++;
//检查并进行双红修正
solveDoubleRed(x);
return x ? x : _hot->parent;
}
叔父节点为黑 u->color==B
此时x、p、g的四个孩子全为黑(可能是外部节点),且黑高度相同
做提升变换后可以发现此时的缺陷只是超级节点的关键码出现了问题,我们对相关关键码重新染色即可,如交换p和g的颜色或者x和g的颜色。
实际上我们使用AVL树的3+4重构即可,将节点xpg和四棵子树按中序组合为:T0,a,T1,b,T2,c,T3,b转黑,a或c转红。
出现双红的原因是在某个三叉节点插入红关键码,使得原黑关键码不再居中,调整之后的效果相当于在新的死叉节点中,三个关键码的颜色改为RBR。
叔父节点为红 u->color==R
相当于4阶B树的超级节点发生上溢。
相当于B树找的居中的关键码,上升至父节点,同时发生分裂,但对于红黑树而言只是p与u转黑,g转红。可能会导致g的父节点发生双红,因此可能要继续进行双红调整。最坏终止于树根,这种变换对于B树来说会发生拓扑结构的的变化,但对于响应的红黑树来说,只是进行了至多O(logn)次的染色,因此其拓扑结构的变化仍控制在O(1)。
复杂度分析
重构、染色均属常数时间的操作,红黑树的每一次插入操作,都可在O(logn)的时间内完成,其中至多做O(logn)次染色,一次3+4重构。
算法实现 删除
首先按照BST的常规算法进行,执行r=removeAt(x, _ hot),x由其孩子接替,r有可能是一个并不存在的外部节点,此时有可能出现双红、双黑现象。若x和r有一个红节点存在(不可能都存在),则只需将r染黑。
若被删除节点x,以及其替代者r都是黑色的,则会出现双黑,此时外部节点的黑高度会降低,不再满足红黑树的要求。
类比于B树,相当于x所属节点发生下溢,我们借助B树的下溢修复算法,考察r的父节点,p=r->parent,r的兄弟s= r== p->lc? p->rc :p->c,分四种情况处理。
BB-1 s为黑,且至少有一个红孩子
我们再一次使用3+4重构,t、s、p重命名为a、b、c,r保持黑,a和c染黑,b继承p的原色。如此,红黑树的全局性质得以恢复。
提升后类比于B树:
相当于对超级节点通过关键码的旋转,消除了超级节点的下溢。
BB-2R s为黑,且两个孩子均为黑,p为红
类比于B树,发生了下溢,关键码s独自构成一个超级节点,无法借出节点,此时我们使用合并操作进行修复。可以发现我们需要做的工作是r保持黑,s转红,p转黑,恢复红黑树的全局性质。
上层节点失去一个关键码后,由于p是红色,则其所在的超级节点必至少还有一个黑节点,因此不会发生下溢的传递。
BB-2B s为黑,且两个孩子均为黑,p为黑
类比于B树,发生了下溢,我们执行合并操作,从上层借出一个节点,而且在这种情况下,下溢可能会发生传递,B树的调整方式可能会发生O(logn)次,但对于等效的红黑树来说,将s染红并没有使红黑树的拓扑结构发生变化,因此整个调整过程发生的重构操作依然能够保证。
BB-3 s为红(s的孩子均为黑)
类比于B树,此时的p和s构成一个三分支节点,我们将s转黑,p转红,这样的操作并没有解决黑高度异常的状态,但却使问题转入了其他三种状态(r有了黑兄弟),实际上只可能出现BB-1和BB-2R情况,因此我们只再做一轮调整即可恢复红黑树的全局特性。
总结
易知红黑树的每一次删除操作都可在O(logn)时间内完成,其中制度做O(logn)次重染色、一次3+4重构和一次单旋(常数次结构调整),这便是红黑树优于AVL树的原因,同时这一特点也是实现持久性结构的重要条件。