通过上篇博客知道,二叉搜索树的局限在于不能完成自平衡,从而导致不能一直保持高性能。
AVL树则定义了平衡因子绝对值不能大于1,使二叉搜索树达到了严格的高度平衡。
还有一种能自我调整的二叉搜索树,
红黑树 : 通过标记节点的颜色(红/黑),使其拥有自平衡的二叉搜索树。
红黑树性质 :
- 性质1:每个节点要么是黑色,要么是红色。
- 性质2:根节点是黑色。
- 性质3:每个叶子节点(NIL)是黑色。
- 性质4:每个红色结点的两个子结点一定都是黑色。
- 性质5:所有路径都包含数量相同的黑结点
这些约束强制了红黑树的关键性质: 红黑树没有一条路径会比其他路径的两倍长(同一起点)。所以这个树大致上是平衡的,不会像二叉搜索树出现极端情况。
是性质4和5导致路径上确保了这个结果。最短的路径只有黑色节点,最长路径有交替的红色和黑色节点。因为所有的路径黑色节点数量相同,所以没有路径能多于任何其他路径的两倍长。
红黑树节点定义:
enum Colour { RED, BLACK, }; template<class K,class V> class RBtreeNode { RBtree<K,V>* _left; RBtree<K,V>* _right; RBtree<K,V>* _parent; pair<K,V> _kv; Colour _col; }; template<class K,class V> class RBTree { typedef RBtreeNode<K,V> Node; public: bool Insert(const pair<K,V>& kv); private: Node* _root = nullptr; }
红黑树的插入:
在结点插入后,需要遵循红黑树性质
新结点默认是红色,所以需要判断红黑树的性质是否遭到破坏(插入节点与父亲节点都为红色,违反性质4)
有以下三种情况:
1.cur和p为红,g为黑,u为红 ---> 将p,u变黑 , g变红, cur变为g,继续向上调整
2.cur和p为红,g为黑,u不存在/为黑 ---> 如果p为g的左孩子,cur为p的左孩子 --- 左左 , g右单旋,g变红,p变黑
p为g的右孩子,cur为p的g右孩子--- 右右 , g左单旋,g变红,p变黑
3.cur和p为红,g为黑,u不存在/为黑 --- 如果p为g的左孩子,cur为p的右孩子--- 左右: p左旋
p为g的右孩子,cur为p的左孩子--- 右左: p右旋
--->变成情况2处理
插入新结点代码:
bool Insert(const pair<K,V> _kv) { //插入结点 if(root == nullptr) { root=new Node(kv); root->_col = BLACK; return true; } Node* parent = nullptr; Node* cur = root; while(cur) { if(cur->kv.first < kv.first) else if(cur->kv.first > kv.first) else return false; } cur = new Node(kv); cur->_col = RED; //父节点连接插入的结点 if(parent->kv.first < kv.first) { parent->_right = cur; cur->_parent = parent; } else { ... } //颜色调整 //红黑树遭到破坏: 红红相连 while(parent && parent->_col==RED) { Node* g = parent->_parent; //叔叔在右边 if(parent == g->_left) { Node* u = g->_right; //叔叔为红,变色调整 if(u && u->_col==RED) { parent->_col = BLACK; u->_col = BLACK; g->_col = RED; cur = g; parent = cur->_parent; } //叔叔非红,旋转调整 else { //父亲和孩子没有对齐,左旋变齐 if(cur == parent->_right) { RotateL(parent); swap(parent,cur); } //对齐,右旋并调色,完成调整 RotateR(g); parent->_col = BLACK; g->_col = RED; break; }//end of 叔非红处理 } //叔叔在左边 else { //.... } }//end of 红红相连 //按规定将其根置为黑 : 防止根为cur情况 _root->_col = BLACK; return true; }