红黑树
红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
例如:
下图就是一个红黑树,同时也是一颗二叉搜索树
和AVL树不同的是,AVL树依靠着平衡因子的限制的平衡性比红黑树要更高
红黑树的性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
我们要尤其注意一个点:
红黑树的叶子节点是空节点,而非我们之前传统意义上的叶子节点
对于性质3的简化就是,一条路径上不能出现连续的红节点
基于上面给出的性质,大家可以思考一个问题:
那么一颗红黑树的最短路径和最长路径分别有什么特点呢?
大家仔细思考就会返现,由于每条路径必须要有相同的黑节点,且不能出现连续的红节点,就可以总结出来:
最短路径全是黑节点,最长路径是一黑一红节点相间
可能大家会有一个问题:为什么有了AVL树还有搞一个红黑树呢,而且他的平衡性还没有AVL树高
确实红黑树的搜索时间复杂度没有AVL树这么快,但是红黑树的搜索效率和AVL树可以近似看作相等,但是红黑树不需要那么多的旋转来调平衡,因为红黑树可以允许最长路径是最短路径的2倍,他的要求并没有AVL树那么严格,所以红黑树的旋转次数要比AVL树少很多,效率自然就提升了,故而实际应用中红黑树要比AVL树用的更多一些。map和set的底层实现都是用红黑树而并非AVL树所实现的。
红黑树的定义
根据上面的红黑树的性质和我们之前学习的AVL树的知识的铺垫,我们就可以很快的将红黑树的基本框架搭起来:
与AVL树的平衡因子不同,红黑树除了节点外还要枚举节点的颜色
我们将黑色和红色先进行枚举
enum Color { RED, BLACK };
然后进行树的节点的定义:
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{RBTreeNode(const ValueType& data = ValueType(),Color color = RED): _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _color(color){}RBTreeNode<ValueType>* _left; // 节点的左孩子RBTreeNode<ValueType>* _right; // 节点的右孩子RBTreeNode<ValueType>* _parent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)ValueType _data; // 节点的值域Color _color; // 节点的颜色
};
大家可以看到我们在节点的颜色的初始化时的时候给了缺省参数是红色,这是为什么呢?
大家可以想一想,如果我们给的节点是黑色,那么无论新增到哪一颗红黑树上都会导致这颗红黑树不平衡,但是使用红节点只是可能导致不平衡,所以我们当然优先使用红节点!
例如:
下图中新增红节点不一定会导致红黑树不平衡,但是如果新增的节点颜色是黑色,那么一定要进行操作来保持这棵树的平衡
红黑树的插入
和AVL树一样,红黑树的插入操作可以分为两步:
1.按照二叉搜索的树规则插入新节点
我们先插入节点,不管平衡性
template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:bool insert(const T& key){if (_root == nullptr){_root = new Node(key);_root->_color = RED;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_data > key){parent = cur;cur = cur->_left;}else if (cur->_data < key){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(key);cur->_color = RED;if (parent->_data > key){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}}private:Node* _root = nullptr;};
2. 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
由于需要双亲节点的兄弟和父亲节点,我们制定了简写:
cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
注意,下面所有图中的树可能是一整棵树,也有可能是一颗子树
情况一: cur为红,p为红,g为黑,u存在且为红
将u和p变黑,g变红,如果g是根节点的话,调整完成后g节点必须变黑
如果g还有双亲节点的话,并且g的parent是红色的话就继续向上调整
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2
完整插入代码如下:
bool insert(const T& key)
{if (_root == nullptr){_root = new Node(key);_root->_color = RED;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_data > key){parent = cur;cur = cur->_left;}else if (cur->_data < key){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(key);cur->_color = RED;if (parent->_data > key){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}while (parent && parent->_color == RED){Node* grandfather = parent->_parent;//parent是g的左孩子if (parent->_left == grandfather->_left){//结构// g// p u//cNode* uncle = grandfather->_right;if (uncle && uncle->_color == RED){//进行变色parent->_color = uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;}//uncle不存在else{if (cur == parent->_left){//单旋// g// p//cRotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else{//双旋// g// p// cRotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}else // parent == grandfather->_right {// g // u p// c Node* uncle = grandfather->_left;if (uncle && uncle->_color == RED){// 变色 parent->_color = uncle->_color = BLACK;grandfather->_color = RED;// 继续往上处理 cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else{// g // u p // c // RotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}_root->_color = BLACK;return true;}
}
红黑树的迭代器
迭代器是很重要的一个部分,没有迭代器一切容器都不好用
template<class T>
struct _treeiterator
{typedef RBTreeNode<T> Node;typedef _treeiterator<T> Self;Node* _node;_treeiterator(Node* node):_node(node){ }T& operator*(){return _node->_data;}T* operator&(){return &_node->_data;}Self operator++(){if (_node->_right){Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}else{Node* cur = _node->_left;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator != (const Self& s){return _node != s._node;}bool operator == (const Self & s){return _node == s._node;}
};typedef treeiterator<T> iterator;iterator begin()
{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);
}iterator end()
{return iterator(nullptr);
}
红黑树的验证
红黑树的检测分为两步:
1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2. 检测其是否满足红黑树的性质
由于博主的能力有限,本篇博文的分享到这里就结束了,感谢大家的支持!