红黑树
- 概念与性质
- 树节点的定义
- 插入
- 红黑树的验证
- 红黑树与AVL树的对比
概念与性质
概念:
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
性质:
- 每个结点不是红色就是黑色 。
- 根节点是黑色的 。
- 如果一个节点是红色的,则它的两个孩子结点是黑色的。(这里的意思是两个红色节点不能连接到一起)
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。(这里的意思是每条路径的黑色节点数形同)
- 每个叶子结点都是黑色的。(此处的叶子结点指的是空结点,也可以叫做NIL结点)
为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
我们来考虑最好和最坏的情况:
最好:全都是黑节点,这是满二叉树。时间复杂度:O(logN)
最坏:最不平衡,但是又不破坏性质,也就是比最好情况多找一倍的量,2 logN,这个量对于计算机而言是非常容易的。时间复杂度:O(logN)
对于这两种情况,如果其中一条路径添加黑结点,那么其他路径也要添加黑结点,如果是最坏的情况22号结点添加了一个红色结点是不允许的,所以只要遵循红黑树的性质就不会右最长路径超过最短路径二倍的问题。
这棵树和AVL树不同的是没有平衡因子,多了一个颜色变量。
相比较于AVL树,红黑树的旋转更少一些,AVL总是要旋转,也是会影响效率的。
树节点的定义
enum Color//利用枚举来给红黑树配色
{RED,BLACK
};
template<class K, class V>
struct RBTreeNode
{RBTreeNode(const T& data):_data(data), _color(RED)//这里一定要给红色,如果给黑色其他路径就要涉及到也要加黑色结点,更加麻烦,_left(nullptr),_right(nullptr),_parent(nullptr){}RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair<K, V> _data;Color _color;//结点的配色
};
插入
插入的第一步和搜索二叉树的一样,先找到合适的位置。
第二步就是判断是否符合红黑树的性质,因为插入的新节点是红色的,所以分为以下几个情况考虑。
图中的g代表grandfather,p是parent,u是uncle,c是cur。
情况一: cur为红,p为红,g为黑,u存在且为红。
c为新增节点,p为红色,u也为红色,g点为黑色,这个时候应该将p和u变成黑色,g变成红色。
第一种情况,无论c插入的地方是p的子树还是u的子树都无所谓。
这里还要注意,如果g结点的上面还是红色结点,这里就需要继续向上调整。
这个时候就要将g赋值给c,p等于c的parent指向的结点。
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑。
首先对g右单旋,然后将p变为红色,g变为黑色。
这个情况无论是对于局部树还是根节点都不会使上面的发生任何变化,因为原来g结点的位置变成了p结点,但是颜色还是黑色。
这里就算u结点什么也没有,也是按照旋转+变色的方式来解决问题。
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;
相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2
其实这三种情况看下来之后我们发现,最重要的是叔叔节点。
代码实现:
bool Insert(const pair<K, V>& data){if (_root == nullptr){_root = new Node(data);_root->_color = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_data.first > data.first){parent = cur;cur = cur->_left;}else if (cur->_data.first < data.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(data);//这里默认是红色结点if (parent->_data.first > data.first){cur->_parent = parent;parent->_left = cur;}else if (parent->_data.first < data.first){cur->_parent = parent;parent->_right = cur;}//如果父节点为空就代表cur是根节点,没必要调整了//还要判断cur结点是否与父节点的颜色均为红色while (parent && parent->_color == RED){Node* grandfather = parent->_parent;//祖父结点if (parent == grandfather->_left)//新增结点在祖父左{Node* uncle = grandfather->_right;//情况一if (uncle && uncle->_color == RED)//这里不要忘记验证uncle的存在{parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;//最后让cur等于祖父结点的位置parent = cur->_parent;}else{if (parent->_left == cur)//情况二{RotateR(grandfather);//右单旋grandfather->_color = RED;parent->_color = BLACK;}else if (parent->_right == cur)//情况三{RotateL(parent);//左单旋RotateR(grandfather);//右单旋cur->_color = BLACK;grandfather->_color = RED;}break;//第二种和第三种情况变完之后因为最上面的组节点变为黑,所以这里跳出循环}}else//新增结点在祖父右{Node* uncle = grandfather->_left;if (uncle && uncle->_color == RED)//情况一{uncle->_color = BLACK;parent->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;}else {if (cur == parent->_right)//情况二{RotateL(grandfather);grandfather->_color = RED;parent->_color = BLACK;}else if (cur == parent->_left)//情况三{RotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}}_root->_color = BLACK;return true;}void RotateL(Node* parent)//左单旋{Node* pparent = parent->_parent;Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (pparent){if (pparent->_left == parent)pparent->_left = subR;elsepparent->_right = subR;}else{_root = subR;}subR->_parent = pparent;}void RotateR(Node* parent)//右单旋{Node* pparent = parent->_parent;Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (pparent){if (pparent->_left == parent)pparent->_left = subL;elsepparent->_right = subL;}else{_root = subL;}subL->_parent = pparent;}
红黑树的验证
红黑树的验证需要去检验不能是两个连续的红色结点,并且每条路径上的黑色节点相同。
这里解决的方法是添加两个参数,一个参数用来计算每条路径上的黑色结点,另外一个值用来储存某一条路径的黑色结点的个数。
bool _IsBalanceTree(Node* root, int k, int sum)//验证{if (root == nullptr){if (k == sum)//这里代表当前路径点和最左边的路径点相同return true;else{cout << "每条路径上黑色结点数量不同" << endl;}return false;}if (root->_color == BLACK)k++;if (root->_parent && root->_parent->_color == RED && root->_color == RED){cout << root->_parent->_data.first << endl;return false;}return _IsBalanceTree(root->_left, k, sum)&&_IsBalanceTree(root->_right, k, sum);}
红黑树与AVL树的对比
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中黑树更多。