1.定义:红黑树是一颗二叉查找树,但是每一个节点多加上了一个变量来标识每一个节点的颜色,我们需要这个颜色来规定一些关于红黑树的规则,我们在构造红黑树的时候,需要每个时候都要保持这些规则:
1)每个节点不是黑色就是红色
2)根节点为黑色
3)每个叶节点是黑色的
4)没有两个红色节点相邻
5)对于每一个节点,从该节点到期所有后代叶节点的简单路径上,都包含相同数目的黑色节点
我们需要一直保持着这五个规则,不管是插入节点,还是删除节点的时候,这样我们就可以得到一颗近似平衡的查找二叉树。
为什么说是近似平衡的?因为红黑树不像AVL树,AVL树是高度平衡的二叉查找树,对于每一个节点,左右子树的高度最多相差1,但是红黑树是黑高平衡的,即第五条规则。红黑树会确保没有一条路径会比其他路径长处2倍。
我们比较关心的是红黑树的效率问题:红黑树可以保证在最坏情况下都可以在O(lgN)的时间内完成插入,查找,删除操作。
首先,我们先定义节点和颜色:
enum COLOR {
BLACK = 1,
RED = 2,
};
template <class Key, class Value>
struct Node {
Node(Key k, Value v) : key(k), value(v), color(RED), parent(NULL), left(NULL), right(NULL) {}
Node() : color(BLACK), parent(NULL), left(NULL), right(NULL) {}
~Node() {}
Key key;
Value value;
COLOR color;
Node<Key, Value>* parent;
Node<Key, Value>* left;
Node<Key, Value>* right;
};
节点类跟二叉搜索树一样,但是多出了一个颜色成员。
因为红黑树是在二叉搜索树的基础上面加上了颜色,所以保留了很多二叉搜索树的操作,接下来我们定义红黑树类:
template <class Key, class Value>
class RBTree {
public:
RBTree() : root(NULL) {NIL = new Node<Key, Value>();}
~RBTree() {}
Node<Key, Value>* insert(Node<Key, Value>* node);
void printTree();
void erase(Key key);
Node<Key, Value>* find(Key key);
private:
void rotateLeft(Node<Key, Value>* node);
void rotateRight(Node<Key, Value>* node);
void adjustTree(Node<Key, Value>* node);
void printTree(Node<Key, Value>* node);
Node<Key, Value>* findNext(Node<Key, Value>* node);
Node<Key, Value>* findMin(Node<Key, Value>* node);
void dealOneChild(Node<Key, Value>* node, Node<Key, Value>* child);
void adjustDelete(Node<Key, Value>* node, Node<Key, Value>* realParent);
Node<Key, Value>* root;
Node<Key, Value>* NIL; //哨兵
};
在上面的类定义里面,相比于二叉搜索树来说,多出了以下的操作和成员:
void adjustTree(Node<Key, Value>* node);
void dealOneChild(Node<Key, Value>* node, Node<Key, Value>* child);
void adjustDelete(Node<Key, Value>* node, Node<Key, Value>* realParent);
Node<Key, Value>* NIL; //哨兵
其中,adjustTree是在插入节点的时候可能会调用这个函数,因为插入节点可能会破坏红黑树的五条规则其中的一个或者几个。
dealOneChild是用来操作两个参数的父指针的指向。
adjustDelete是在删除一个节点的时候会调用。
哨兵是一个特殊的节点,颜色永远是黑色的,而且其他的成员取值没有特别规定,它在类中充当了叶节点和根节点的父节点的角色(其实我们也可以使用nullptr NULL之类的常量来表示)