> 文章列表 > 【数据结构】简单快速过一遍红黑树

【数据结构】简单快速过一遍红黑树

【数据结构】简单快速过一遍红黑树

文章目录

  • 红黑
    • 1 红黑树的概念
    • 2 红黑树的性质
    • 3 红黑树节点的定义
    • 4 红黑树的插入操作
    • 5 红黑树的验证
    • 6 红黑树与AVL树的比较
    • 7.C++实现红黑树

红黑树

1 红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

【数据结构】简单快速过一遍红黑树

2 红黑树的性质

  1. 每个结点不是红色就是黑色

  2. 根节点是黑色的

  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点

  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

  • 举个极端一点的例子就清楚了,树的一边全是黑节点,另一边是一黑一红交替,那么全黑的那一边一定是最短的那个,一黑一红交替一定是最长的那个,这时候刚好是2倍关系

【数据结构】简单快速过一遍红黑树


3 红黑树节点的定义

enum Color
{RED,//red红色BLACK,//black黑色
};template<class T>
struct RBTreeNode
{//在节点的定义中,为什么要将节点的默认颜色给成红色的?---因为这样违反的红黑树规则最少,方便处理RBTreeNode(const T& val,Color color=RED):_val(val), _left(nullptr), _right(nullptr), _parent(nullptr), _color(color){}T _val;// 节点的值RBTreeNode<T>* _left;// 节点的左孩子RBTreeNode<T>* _right;// 节点的右孩子RBTreeNode<T>* _parent;// 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)Color _color;// 节点的颜色
};

4 红黑树的插入操作

红黑树同样也是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点
  2. 检测新节点插入后,红黑树的性质是否造到破坏
    因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何
    性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连
    在一起的红色节点,此时需要对红黑树分情况来讨论:
    约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
    • 情况一: cur为红,p为红,g为黑,u存在且为红 ----p(parent),g(grandparent),u(uncle)

【数据结构】简单快速过一遍红黑树

cur和p均为红,违反了性质三,此处能否将p直接改为黑?—不能
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

  • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

【数据结构】简单快速过一遍红黑树

  • 情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

【数据结构】简单快速过一遍红黑树


5 红黑树的验证

红黑树跟AVL树的代码一样复杂,那我们怎么知道自己写的代码有没有问题呢?

同样也可以写一个小代码来检验一下是否符合红黑树性质即可

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

  2. 检测其是否满足红黑树的性质

bool IsValidRBTree()
{if (_root == nullptr)//空树也是红黑树return true;if (_root->_color != BLACK){cout << "违反了红黑树性质二:根节点必须为黑色" << endl;return false;}//获取任意一个节点的黑色节点size_t blackCount = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK)blackCount++;cur = cur->_left;}// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数size_t k = 0;return IsValidRBTree(_root, blackCount, k);
}
bool IsValidRBTree(Node* root, size_t blackCount, size_t k)
{//走到null之后,判断k和black是否相等if (root == nullptr){if (k != blackCount){cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;return false;}return true;}// 统计黑色节点的个数if (root->_color == BLACK)k++;// 检测当前节点与其双亲是否都为红色Node* parent = root->_parent;if (parent && parent->_color == RED&& root->_color == RED){cout << "违反性质三:不能存在连在一起的红色节点" << endl;return false;}return IsValidRBTree(root->_left, blackCount, k) &&IsValidRBTree(root->_right, blackCount, k);
}

6 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

总之一句话:

实际应用中,若搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择红黑树。

AVL树传送门


7.C++实现红黑树

#pragma once 
#include <iostream>
#include <ctime>
using namespace std;namespace hdm
{enum Color{RED,//red红色BLACK,//black黑色};template<class T>struct RBTreeNode{//在节点的定义中,为什么要将节点的默认颜色给成红色的?---因为这样违反的红黑树规则最少,方便处理RBTreeNode(const T& val, Color color = RED):_val(val), _left(nullptr), _right(nullptr), _parent(nullptr), _color(color){}T _val;// 节点的值RBTreeNode<T>* _left;// 节点的左孩子RBTreeNode<T>* _right;// 节点的右孩子RBTreeNode<T>* _parent;// 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)Color _color;// 节点的颜色};template<class T>class RBTree{public:typedef RBTreeNode<T> Node;bool Insert(const T& x){if (_root == nullptr){_root = new Node(x);_root->_color = BLACK;//规定根节点都是黑色return true;}Node* cur = _root;Node* parent = cur;//记录parent方便后续插入while (cur){//cur往子树走的同时记录子树的父节点if (cur->_val > x)//比cur小往左子树找{parent = cur;cur = cur->_left;}else if (cur->_val < x)//比cur大往右子树找{parent = cur;cur = cur->_right;}else//进入else说明cur->_val==x,表示已经存在该节点,不需要插入,返回false表示插入失败{return false;}}//程序走到这说明cur=nullptr,最后x就应该要插入在parent的下面,至于是插入左边还是右边//具体看x与parent->_val 之间值的关系cur = new Node(x);cur->_color = RED;//默认插入红色节点if (parent->_val > x)//如果x的值小于parent就往左边插入{parent->_left = cur;}else//如果x的值大于parent就往右边插入{parent->_right = cur;}cur->_parent = parent;//判断是否符合红黑树规则,没有则要改颜色while (parent&& parent->_color == RED){//大致分两个大种情况://1.叔叔节点存在且为红---看图//2.叔叔节点不存在或者存在且为黑Node* grandparent = parent->_parent;if (grandparent->_left == parent)//确定叔叔节点的位置{Node* uncle = grandparent->_right;if (uncle && uncle->_color == RED){//情况一:叔叔节点存在且为红//处理方式:变色处理parent->_color = uncle->_color = BLACK;grandparent->_color = RED;//这种情况因为改了祖父节点的颜色有可能影响到其他节点,所以要继续向上调整cur = grandparent;parent = cur->_parent;}else{//有可能是uncle节点不存在,也有可能是uncle是黑节点---对应就是情况二和三//uncle节点可能不存在或者为黑节点if (parent->_left == cur)//cur与parent是同侧{//右旋+变色RotateR(grandparent);parent->_color = BLACK;grandparent->_color = RED;}else//cur和parent不是同一侧--情况三{//左右旋+变色RotateL(parent);RotateR(grandparent);cur->_color = BLACK;grandparent->_color = RED;}break;}}else//grandparent->_right == parent---其实跟上面的情况类似,只不过方向相反{Node*uncle = grandparent->_left;if (uncle&& uncle->_color == RED)//情况一:叔叔节点存在且为空{uncle->_color = parent->_color = BLACK;grandparent->_color = RED;//继续向上调整cur = grandparent;parent = cur->_parent;}else{//可能是情况二或者三if (parent->_right == cur)//当cur与parent是同侧的时候{//左旋+变色RotateL(grandparent);parent->_color = BLACK;grandparent->_color = RED;}else//不同侧{//右左旋转+变色RotateR(parent);RotateL(grandparent);cur->_color = BLACK;grandparent->_color = RED;}break;}}	}_root->_color = BLACK;//因为上面的操作有可能把跟节点变红,不管怎么样直接加这句代码就不用管它不可能出现根是红的情况return true;}void InorderTree(){InorderTree(_root);cout << endl;}bool IsValidRBTree(){if (_root == nullptr)//空树也是红黑树return true;if (_root->_color != BLACK){cout << "违反了红黑树性质二:根节点必须为黑色" << endl;return false;}//获取任意一个节点的黑色节点size_t blackCount = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK)blackCount++;cur = cur->_left;}// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数size_t k = 0;return IsValidRBTree(_root,blackCount,k);}private:bool IsValidRBTree(Node* root,size_t blackCount,size_t k){//走到null之后,判断k和black是否相等if (root == nullptr){if (k != blackCount){cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;return false;}return true;}// 统计黑色节点的个数if (root->_color == BLACK)k++;// 检测当前节点与其双亲是否都为红色Node* parent = root->_parent;if (parent && parent->_color == RED&& root->_color == RED){cout << "违反性质三:不能存在连在一起的红色节点" << endl;return false;}return IsValidRBTree(root->_left,blackCount,k) && IsValidRBTree(root->_right,blackCount,k);}void InorderTree(Node* root){if (root == nullptr)return;InorderTree(root->_left);cout << root->_val << " ";InorderTree(root->_right);}void RotateL(Node* parent)//左旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;if (subRL)//如果存在subRL,就把它的父子关系连上subRL->_parent = parent;parent->_right = subRL;subR->_left = parent;parent->_parent = subR;if (pparent == nullptr)//pparent为空表示parent为根节点{_root = subR;_root->_parent = nullptr;}else{//pparent存在,要判断parent之前是位置pparent的那一边if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}}subR->_parent = pparent;}void RotateR(Node* parent)//右旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;if (subLR)//如果存在subLR,就把它的父子关系连上subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (pparent == nullptr)//说明parent是根节点{_root = subL;_root->_parent = nullptr;}else{//pparent存在,要判断parent之前是位置pparent的那一边if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}}subL->_parent = pparent;}private:Node* _root=nullptr;};void RBTreeTest1(){int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };//	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };RBTree<int> t;for (auto e : a){if (e == 13)int i = 0;t.Insert(e);}t.InorderTree();}void RBTreeTest2(){srand(time(0));const size_t N = 10000;RBTree<int> t;for (size_t i = 0; i < N; ++i)//用1w个随机数测试插入是否符合红黑树规则{size_t x = rand();t.Insert(x);//cout << t.IsBalance() << endl;}t.InorderTree();cout << t.IsValidRBTree() << endl;}
}