和二叉树不一样,2-3树每个节点保存1个或者2个的key。
对于普通的2节点(2-node),要有1个key和左右两个子节点。
对应3节点(3-node),要有两个Key和三个子节点。
2-3查找树的定义如下:
(1)要么为空,要么:
(2)对于2节点,该节点保存一个key及对应value,以及两个指向左右节点的节点,左节点也是一个2-3节点,所有的值都比key有效,有节点也是一个2-3节点,所有的值比key要大。
(3)对于3节点,该节点保存两个key及对应value,以及三个指向左中右的节点。左节点也是一个2-3节点,所有的值均比两个key中的最小的key还要小;中间节点也是一个2-3节点,中间节点的key值在两个跟节点key值之间;右节点也是一个2-3节点,节点的所有key值比两个key中的最大的key还要大。
2-3树的查找和二叉查找树类似,要确定一个树是否属于2-3树,我们首先和其跟节点进行比较,如果相等,则查找成功;否则根据比较的条件,在其左中右子树中递归查找,如果找到的节点为空,则未找到,否则返回。
将一个4-node拆分为2-3node涉及到6种可能的操作。这4-node可能在跟节点,也可能是2-node的左子节点或者右子节点。或者是一个3-node的左,中,右子节点。所有的这些改变都是本地的,不需要检查或者修改其他部分的节点。所以只需要常数次操作即可完成2-3树的平衡。
注:2-3树之所以能够保证在最差的情况下的效率的原因在于其插入之后仍然能够保持平衡状态。
这些本地操作保持了2-3树的平衡。对于4-node节点变形为2-3节点,变形前后树的高度没有发生变化。只有当跟节点是4-node节点,变形后树的高度才加一。
红黑树是一种具有红色和黑色链接的BST。
《算法导论》中对红黑树的定义(针对的是节点颜色,基于2-3-4数):
(1)节点是红色或黑色树
(2)根节点和叶子节点(NIL节点)都是黑色。
(3)每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点,也就是没有5-node)
(4)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。(黑色节点完美平衡)
《算法》中对红黑树的定义(针对的是线的颜色,基于2-3树):
(1)红色的线永远是左侧链接。(强行的规定)
(2)没有一个节点同时链了两条红色的线。(也就是没有4-node)
(3)任何从根到叶子节点的路径上有相同颜色的黑线。(黑线节点完美平衡)
2-3查找树能保证在插入元素之后能保持树的平衡状态,最坏情况下即所有的子节点都是2-node,树的高度为lgN,从而保证了最坏情况下的时间复杂度。但是2-3树实现起来比较复杂。红黑树是一种简单实现2-3树的数据结构。
对2-3查找树中的3-nodes节点添加额外的信息。
红黑树中将节点之间的链接分为两种不同类型。
红色链接:链接两个2-nodes节点来表示一个3-nodes节点。
黑色链接:链接普通的2-3节点。
从边的颜色到节点的颜色
BST的每一个节点上增加一个新的表示颜色的标记。该标记指示该节点指向其父节点的颜色。
- 右旋
将一个向左倾斜的红色链接旋转为向右链接,将红线链接的两个节点中的一个较大的节点移动到根节点上。
当出现一个临时的4-node的时候,即一个节点的两个子节点均为红色。子节点颜色为黑色,父节点的颜色设置为红色;反之亦然。
因为2是根节点,所以要根黑
二叉树中,根节点永远是黑色。
#include
using namespace std;
enum COLOR {RED,BLACK};
template <typename T>
class _Node {
public:
T val;
COLOR color = BLACK;
_Node* left;
_Node* right;
_Node(const T& val):val(val),left(nullptr),right(nullptr) {}
_Node(const T& val,_Node* left,_Node* right):val(val),left(left),right(right) {}
};
typedef _Node<int> Node;
// P Q
// / \ / \
// q C A p
// / \ / \
// A B B C
Node* RotateRight(Node* root) {
Node* p = root;
Node* q = p->left;
Node* b = q->right;
q->right = p;
p->left = b;
q->color = p->color;
p->color = RED;
return q;
}
// P Q
// / \ / \
// A q p C
// / \ / \
// B C A B
Node* RotateLeft(Node* root) {
Node* p = root;
Node* q = p->right;
Node* b = q->left;
q->left = p;
p->right = b;
q->color = p->color;
p->color = RED;
return q;
}
bool IsRed(Node* root) {
return nullptr!=root && root->color==RED;
}
Node* FiipColor(Node* root) {
if(IsRed(root->left) && IsRed(root->right)) {
root->left->color = BLACK;
root->right->color = BLACK;
root->color = RED;
}
if(!IsRed(root->left) && !IsRed(root->right)) {
root->left->color = RED;
root->right->color = RED;
root->color = BLACK;
}
return root;
}
// P P P B
// / \ / \ / \ / \
// A Q a q a b p q
// / / \ /
// b b q a
Node* MoveRedLeft(Node* root){
root = FiipColor(root);
if(IsRed(root->right->left)){
root->right = RotateRight(root->right);
root = RotateLeft(root);
}
return root;
}
// P P Q
// / \ / \ / \
// Q B q b a p
// / / \
// a a b
Node* MoveRedRight(Node* root){
root = FiipColor(root);
if(IsRed(root->left->left)){
root = RotateRight(root);
}
return root;
}
void Preorder(Node* root) {
if(nullptr == root) return;
if(root->left) {
cout << root->val << "--" << root->left->val;
if(root->left->color==RED) cout << "[color=red]";
cout << endl;
}
if(root->right) {
cout << root->val << "--" << root->right->val;
if(root->right->color==RED) cout << "[color=red]";
cout << endl;
}
Preorder(root->left);
Preorder(root->right);
}
Node* Balance(Node* root) {
if(IsRed(root->right) && !IsRed(root->left)) root = RotateLeft(root);
if(IsRed(root->left) && IsRed(root->left->left)) root = RotateRight(root);
if(IsRed(root->left) && IsRed(root->right)) root = FiipColor(root);
return root;
}
int main() {
Node a(1);
Node b(2);
Node c(3);
c.color = RED;
a.right = &b;
b.right = &c;
Preorder(&a);
a.right = RotateLeft(&b);
Preorder(&a);
a.right = RotateRight(&c);
Preorder(&a);
}
1--2
2--3[color=red]
1--3
3--2[color=red]
1--2
2--3[color=red]
一切为了平衡,树根到叶子节点的路径上黑边的个数(黑节点个数)决定平衡。
从上到下查找位置,插入节点为红叶子节点,然后从下到上回溯平衡。
3-node节点底部插入新的节点,可能会导致父节点不满足红黑树的特点(完美平衡),可以从下往上回溯更新节点的颜色(与AVL判断平衡因子更新树高相似),直到平衡。
红黑树能够保证符号表的所有操作即使在最坏的情况下都能保证对数的时间复杂度,也就是树的高度。
与BST一致。
删除需要保证删除节点是非2-node的叶子节点,即必须是3-node和4-node。所以查找过程需要把过程中的2-node转化成3-node和4-node,删除后,在把3-node和4-node还原。
#include
#include
#include
#include
using namespace std;
enum COLOR {RED,BLACK};
template <typename T>
class _Node {
public:
T val;
_Node* left;
_Node* right;
COLOR color = RED;
_Node(const T& val):val(val),left(nullptr),right(nullptr) {}
_Node(const T& val,_Node* left,_Node* right):val(val),left(left),right(right) {}
};
template <typename T>
class RBTree {
typedef _Node<T> Node;
Node* m_root = nullptr;
public:
bool Search(const T& val) {
return Search(m_root,val);
}
void Insert(const T& val) {
if(Search(val)) return;
m_root = Insert(m_root,val);
m_root->color = BLACK;
}
void Remove(const T& val) {
m_root->color = RED;
m_root = Remove(m_root,val);
m_root->color = BLACK;
}
void Print() {
Preorder(m_root);
}
private:
bool Search(Node* root,const T& val) {
if(nullptr == root) return false;
if(root->val == val) return true;
if(root->val < val) {
return Search(root->right,val);
} else {
return Search(root->left,val);
}
// return Search(root->valright:root->left,val);
}
Node* Remove(Node* root,const T& val) {
if(nullptr == root) return nullptr;
if(root->val > val) {
if(!IsRed(root->left) && !IsRed(root->left->left)) root = MoveRedLeft(root);
root->left = Remove(root->left,val);
}else {
if(IsRed(root->left)) root = RotateRight(root);
if(val == root->val && nullptr == root->right) {delete root; return nullptr;}
if(!IsRed(root->right) && !IsRed(root->right->left)) root = MoveRedRight(root);
if(val == root->val){
T minval = Minimun(root->right);
root->val = minval;
root->right = Remove(root->right,minval);
// root->right = DeleteMin(root->right);
}else{
root->right = Remove(root->right,val);
}
}
return Balance(root);
}
Node* DeleteMin(Node* root) {
if(nullptr == root) throw runtime_error("root is null");
Node* prev = nullptr;
Node* cur = root;
while(nullptr != cur->left) {
prev = cur;
cur = cur->left;
}
delete cur;
return root;
}
T Minimun(Node* root) {
if(nullptr == root) throw runtime_error("root is null");
while(nullptr != root->left) root = root->left;
return root->val;
}
T Maximun(Node* root) {
if(nullptr == root) throw runtime_error("root is null");
while(nullptr != root->right) root = root->right;
return root->val;
}
Node* Insert(Node* root,const T& val) {
if(nullptr == root) return new Node(val);
if(root->val>val) {
root->left = Insert(root->left,val);
} else {
root->right = Insert(root->right,val);
}
return Balance(root);
}
// P Q
// / \ / \
// q C A p
// / \ / \
// A B B C
Node* RotateRight(Node* root){
Node* p = root;
Node* q = p->left;
Node* b = q->right;
q->right = p;
p->left = b;
q->color = p->color;
p->color = RED;
return q;
}
// P Q
// / \ / \
// A q p C
// / \ / \
// B C A B
Node* RotateLeft(Node* root){
Node* p = root;
Node* q = p->right;
Node* b = q->left;
q->left = p;
p->right = b;
q->color = p->color;
p->color = RED;
return q;
}
bool IsRed(Node* root){
return nullptr!=root && root->color==RED;
}
Node* FiipColor(Node* root) {
if(IsRed(root->left) && IsRed(root->right)) {
root->left->color = BLACK;
root->right->color = BLACK;
root->color = RED;
}else if(!IsRed(root->left) && !IsRed(root->right)) {
root->left->color = RED;
root->right->color = RED;
root->color = BLACK;
}
return root;
}
// 大写代表黑色,小写代表红色
// P P P B
// / \ / \ / \ / \
// A Q a q a b p q
// / / \ /
// b b q a
Node* MoveRedLeft(Node* root){
root = FiipColor(root);
if(IsRed(root->right->left)){
root->right = RotateRight(root->right);
root = RotateLeft(root);
}
return root;
}
// P P Q
// / \ / \ / \
// Q B q b a p
// / / \
// a a b
Node* MoveRedRight(Node* root){
root = FiipColor(root);
if(IsRed(root->left->left)){
root = RotateRight(root);
}
return root;
}
void Preorder(Node* root){
if(nullptr == root) return;
if(root->left) {
cout << root->val << "--" << root->left->val;
if(root->left->color==RED) cout << "[color=red]";
cout << endl;
}
if(root->right) {
cout << root->val << "--" << root->right->val;
if(root->right->color==RED) cout << "[color=red]";
cout << endl;
}
Preorder(root->left);
Preorder(root->right);
}
Node* Balance(Node* root){
if(IsRed(root->right) && !IsRed(root->left)) root = RotateLeft(root);
if(IsRed(root->left) && IsRed(root->left->left)) root = RotateRight(root);
if(IsRed(root->left) && IsRed(root->right)) root = FiipColor(root);
return root;
}
};
int main() {
RBTree<int> intbst;
int arr[] = {1,3,5,7,2,4,8};
for(auto n:arr){
intbst.Insert(n);
}
intbst.Print();
int n;
while(cin >> n) {
intbst.Remove(n);
intbst.Print();
}
}
结果:
5--3[color=red]
5--8
3--2
3--4
2--1[color=red]
8--7[color=red]
5--2[color=red]
5--8
2--1
2--3
8--7[color=red]
5--3
5--8
3--1[color=red]
8--7[color=red]
操作 | 目的 | 改变项 | 保持项 | 执行条件 |
---|---|---|---|---|
左旋/右旋 | 调节平衡,使之符合红黑树规定 | 节点的位置和颜色 | 始终保持BST特点 | 红节点不满足红黑树规定 |
颜色反转 | 调节平衡,使之符合红黑树规定(等价于2-3树4节点差分) | 改变节点的颜色 | 始终保持BST特点 | 红节点不满足红黑树规定 |
操作 | 目的 | 改变项 | 保持项 | 执行条件 |
---|---|---|---|---|
左旋/右旋 | 旋转红节点到要删除的分支,便于后续删除 | 节点的位置和颜色 | 始终保持BST特点 | 删除右子树节点,左子树的根节点为红节点时。 |
颜色反转 | 增加可使用的红节点(等价于2-3树节点4节点组合) | 改变节点的颜色 | 始终保持BST特点 | 左右子节点为黑节点时 |
红节点移动 | 移动红节点到要删除的分支,便于后续删除 | 节点的位置和颜色 | 始终保持BST特点 | 向下查找删除节点,当前子树连续两个黑节点时。 |