红黑树
两个数据结构,一个是结点
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的参数列表的正确用法。加油!