红黑树
两个数据结构,一个是结点
class Node{
public:
int _data;
Color color;
Node * left, * right, * parent;
Node(int data)
:_data(data){
parent = left = right = nullptr;
color = RED; // 新结点默认为红色
}
};
一个是掌管整个红黑树的rbtree
// 红黑树
class Rbtree
{
private:
Node * root = nullptr;// 根节点
// 以当前结点为基准进行左旋操作
void rotateLeft(Node * y);
// 右旋操作
void rotateRight(Node * y);
// 从根节点开始插入,先区分左右子树
// 然后将其左右子树的首结点作为根节点进行递归
Node * BSTInsert(Node * root, Node * ptr);
// 调整
void fixViolation(Node * ptr);
public:
// 插入,先插入再调整
void insert(const int data);
// 前序遍历
// 使用双端队列
void frontTraverse();
// 层序遍历
void seqTraverse();
};
红黑树最重要的思想就是,用二分查找的方式插入一个结点,这里有一个前提就是在插入结点之前,这个rbtree一定是一个红黑树。此外插入结点的颜色也是红色,因为红色结点不会影响层高,只需要做对应的调整即可。
// 从根节点开始插入,先区分左右子树
// 然后将其左右子树的首结点作为根节点进行递归
Node * BSTInsert(Node * root, Node * ptr)
{
// 如果根节点为空,递归出口
if(root == nullptr)
{
root = ptr;
}
// 左子树
if(ptr->_data < root->_data)
{
// 递归
root->left = BSTInsert(root->left, ptr);
// 如果返回的是新节点,需要初始化它的父节点
root->left->parent = root;
}
// 右子树
else if(ptr->_data > root->_data)
{
// 递归
root->right = BSTInsert(root->right,ptr);
// 如果返回的是新节点,需要初始化它的父节点
root->right->parent = root;
}
return root;
}
调整的思想:
红黑树有一条性质是不能有两个连续的红色结点,因为插入的新结点是红色,所以其父亲结点不能是红色结点,如果是红色结点就需要做调整,而黑色结点不需要。
while(ptr != root && ptr->color != BLACK && ptr->parent->color == RED)
如上循环条件为,ptr不等于根节点,如果是根节点,直接插入即可,不需要做调整。
其次保证当前指向结点的指针不能是黑色,因为插入的结点是红色的,如果当前的结点是黑色的,就意味着不会影响黑高也就没必要再网上递归了。
再然后就是当前结点的父节点是红色,如果是黑色就没有检查的必要了。
在循环判断以后就是对当前结点的父节点进行判断
// 如果父节点是祖父结点的左子节点 if(parent == grandParent->left)
如果是左子节点,就需要根据叔叔结点来判断类型,
如果叔叔结点是红色
此时将叔叔结点和父亲节点都设置为黑色,将祖父节点设置为红色,将祖父节点设置为当前结点,一直向上递归,直到祖父节点是红色(当前节点)。退出循环,判断如果是根节点就将根节点染成黑色。
if(uncle != nullptr && uncle->color == RED)
{
grandParent->color = RED;// 将祖父结点染成红色
// 叔叔和父亲染成黑色
uncle->color = BLACK;
parent->color = BLACK;
ptr = grandParent;// 将当前结点设置为祖父节点,下面的两层已经检查完毕,需要递归的向上检查
}
如果叔叔结点是黑色
旋转的主要目的:将一个结点的右子树或者左子树提升为该结点的父节点。
就需要根据是LR

先对ptr进行左旋,在进行右旋,将ptr旋转到根节点。
还是LL

只进行右旋。
进行调整
else
{
// 祖父节点一定是黑色的
// 如果结点是父节点的右孩子
// 就形成了LR,即左右结构,需要先左旋再右旋
if(ptr == parent->right)
{
// 左旋当前结点,将当前结点提升为根节点
rotateLeft(ptr);
parent = ptr->parent; // parent递归到上一级
}
// ptr是父节点的左孩子,LL结构,只需要右旋即可
rotateRight(ptr); // 右旋当前结点
// 交换当前结点和祖父结点之间的颜色
std::swap(ptr->color, grandParent->color);
// 循环退出
break;
}
如果当前结点的parent是grandParent的右孩子
逻辑翻转
如果叔叔结点是红色,操作一致
否则,根据RL和RR进行旋转,先右旋再左旋


最后再调用插入函数
void insert(const int data)
{
Node * newNode = new Node(data);
root = BSTInsert(root, newNode);
// 调整
fixViolation(newNode);
}
和层序遍历进行打印
// 层序遍历
void seqTraverse()
{
deque<Node*> deq;
// 将根节点入队列
deq.push_back(root);
int len = deq.size();
while(deq.size() != 0)
{
for(int i = 0; i < len; i++)
{
// 出队
Node* curr = deq.front();
// 入队
if(curr->left != nullptr)
deq.push_back(curr->left);
if(curr->right != nullptr)
deq.push_back(curr->right);
deq.pop_front();
cout << curr->_data << "(" << curr->color << ")" << "\t";
}
len = deq.size();
}
cout << "\n";
}
由于国庆回家做了一天的火车,所以有两天没有更新,明天继续坚持,加油,今天手撕了一下红黑树,然后又get到了lambda的参数列表的正确用法。加油!
967

被折叠的 条评论
为什么被折叠?



