接下来我们定义红黑树的插入操作,插入操作的函数原型为:
Node<Key, Value>* insert(Node<Key, Value>* node);
函数接受一个Node参数,并且把这个node插入到红黑树中,返回指向这个node的指针。
我们要完成插入操作需要几个辅助函数,这几个函数为:
void rotateLeft(Node<Key, Value>* node);
void rotateRight(Node<Key, Value>* node);
void adjustTree(Node<Key, Value>* node);
前面两个函数在二叉搜索树中我们已经见过了,是用来对节点进行左旋和右旋的,这两个操作意在对红黑树进行调整,保持相应的高度的平衡。
我们需要特别注意的就是红黑树中新加入的adjustTree这个函数,我们每次插入一个节点的时候,必须是插入一个红色的节点,就因为我们插入一个红色的节点,第四条规则可能会被破坏,所以adjustTree这个函数就是要针对这个情况来对树进行调整。
adjustTree这个函数主要是对红黑性质的维护,可能会被破坏的规则为:2,4
1.当我们的树为空的时候,插入一个红色的节点,那么第二条规则就会被破坏掉,我们只需要简单地把节点颜色转化就行
2.当我们插入的节点的父节点为红色的时候,第四条规则会被破坏,adjustTree大部分的工作就是对这个情况进行处理
当我们的第四条规则被破坏时,我们应该去寻找插入节点的父节点(parent)的兄弟节点(uncle),根据uncle的颜色来进行不同的处理。
那么我们可以得到下面这些情况:
(1)uncle节点是uncle父节点的左子女还是右子女?
(2)uncle节点是红色还是黑色?
(3)新插入的node节点是左子女还是右子女?
其中(1)是互相对称的,我们只需要把左右互换即可。(2)和(3)是关键,(2)(3)组合起来就是三种情况。
①当uncle是红色的时候:parent和uncle都是红色,那么我们把这两个节点都变成黑色,把parent的父节点变成红色,这样就可以在局部维持住红黑树的所有规则。但是parent的父节点变成红色之后,有可能又会违反规则,我们需要把parent的父节点“看作”新插入的节点,重新进行流程。(这个是第一种情况)
②当uncle是黑色的时候:需要判断插入节点是parent的左子女还是右子女(这个是第二种和第三种情况,并且可以相互转化),如果是左子女,那么我们把parent的父节点改成红色,parent改成黑色,针对parent的父节点旋转;如果是右子女,那么我们针对parent进行旋转。
上面的图就是三种情况,上面的图只给出了uncle是右子女的情况,左子女对称,左右互换即可。
下面是左旋,右旋的代码以及插入操作的代码:
rotateLeft:
template <class Key, class Value>
void RBTree<Key, Value>::rotateLeft(Node<Key, Value>* node){
Node<Key, Value>* current = node->right;
node->right = current->left; //设置当前节点的右子女
if (current->left != NULL) {
current->left->parent = node;
}
current->parent = node->parent;
if (node->parent == NIL) { //如果node为根的话,换根
root = current;
} else if (node == node->parent->left) { //不为根,根据node的左右来设置current与parent的关系
node->parent->left = current;
} else {
node->parent->right = current;
}
current->left = node;
node->parent = current;
}
rotateRight:
template <class Key, class Value>
void RBTree<Key,Value>::rotateRight(Node<Key, Value>* node) {
Node<Key, Value>* current = node->left;
node->left = current->right; //设置当前节点的左子女
if (current->right != NULL) {
current->right->parent = node;
}
current->parent = node->parent;
if (node->parent == NIL) { //如果node为根的话,换根
root = current;
} else if (node == node->parent->left) { //不为根,根据node的左右来设置current与parent的关系
node->parent->left = current;
} else {
node->parent->right = current;
}
current->right = node;
node->parent = current;
}
insert:
template <class Key, class Value>
Node<Key, Value>* RBTree<Key, Value>::insert(Node<Key, Value>* node) {
Node<Key, Value>* currentParent = NULL;
Node<Key, Value>* current = root;
Node<Key, Value>* returnNode = node;
while (current != NULL) { //寻找插入的位置
currentParent = current;
if (current->key < node->key) {
current = current->right;
} else if (current->key > node->key) {
current = current->left;
} else {
returnNode = current;
return returnNode;
}
}
if (root == NULL) { //如果树为空的话
root = node;
node->color = BLACK;
root->parent = NIL;
} else {
node->parent = currentParent;
if (node->key > currentParent->key) {
currentParent->right = node;
} else {
currentParent->left = node;
}
}
adjustTree(node); //调整树,不破坏红黑树规则
return returnNode;
}
adjustTree:
template <class Key, class Value>
void RBTree<Key, Value>::adjustTree(Node<Key, Value>* node) {
while (node->parent->color == RED) { //如果父节点是红色才进入这个循环
Node<Key, Value>* current = NULL;
if (node->parent == node->parent->parent->left) { //找到父节点的兄弟节点,这个if else处理的是左右问题
current = node->parent->parent->right;
if (current != NULL && current->color == RED) { //如果父节点的兄弟节点也是红色的话
node->parent->color = BLACK;
node->parent->parent->color = RED;
current->color = BLACK;
node = node->parent->parent;
} else if (node == node->parent->left) { //如果父节点的兄弟节点为黑色,节点为左子女的话,那么旋转
node->parent->parent->color = RED;
node->parent->color = BLACK;
rotateRight(node->parent->parent);
} else { //如果父节点的兄弟节点为黑色,节点为右子女的话
node = node->parent;
rotateLeft(node);
}
} else {
current = node->parent->parent->left;
if (current != NULL && current->color == RED) {
node->parent->color = BLACK;
node->parent->parent->color = RED;
current->color = BLACK;
node = node->parent->parent;
} else if (node == node->parent->right) {
node->parent->color = BLACK;
node->parent->parent->color = RED;
rotateLeft(node->parent->parent);
} else {
node = node->parent;
rotateRight(node);
}
}
}
root->color = BLACK;
}
总结:对于红黑树的插入,总的来说就是两个步骤:二叉搜索树的插入->对于红黑性质的维护。对于第二个步骤,再细分为三种情况:①uncle为红色 ②uncle为黑色,并且插入的节点为parent的左子女 ③uncle为黑色,并且插入的节点为parent的右子女。这三种情况在上面的图中多跟几遍就很清晰了(注意指针的操作)。
(自我理解:为什么要插入红色的节点而不是黑色的节点 : 因为红黑树第五条规则的存在,如果插入黑色的节点,那么这个节点与这个节点的所有祖先节点的第五条规则都会被破坏掉,如果插入红色节点,那么最多就只有第四条规则会被破坏并且影响的就只有一个节点)