【C++】用红黑树封装map、set
用红黑树封装map、set
- 1. 红黑树
- 1.1 模板参数的控制
- 1.1.1 Value
- 1.1.2 KeyOfValue
- 1.2 正向迭代器
- 1.2.1 构造函数
- 1.2.2 begin()+end()
- 1.2.3 operator++()
- 1.2.4 operator--()
- 1.2.5 operator*()
- 1.2.6 operator->()
- 1.2.7 operator==()
- 1.2.8 operator!=()
- 1.2.9 总代码
- 1.3 反向迭代器
- 1.3.1 rbegin()+rend()
- 1.3.2 总代码
- 1.4 find()
- 1.5 insert()
- 2. Map
- 2.1 operator[]
- 3. typename作用
- 4. 完整代码
- 4.1 Map.h
- 4.2 Set.h
- 4.3 RBTree.h
1. 红黑树
- set是K模型,map是KV模型,二者底层都是使用红黑树来实现的,所以我们可以将红黑树设置为模板,即:set、map复用同一个类模板的红黑树。
1.1 模板参数的控制
1.1.1 Value
- Value决定你是k模型的set、还是KV模型的map。
enum Color { //枚举,一一列举出事物具有的所有可能Red, //枚举常量,给枚举变量进行赋值Black,
};template<class T>//红黑树的节点
struct RBTreeNode {typedef RBTreeNode<T> Node;//三叉链-》优点:便于查找孩子、父亲节点Node* _left; //该节点的左孩子Node* _right; //该节点的右孩子Node* _parent; //该节点的父亲,便于向上更新T _data;Color _col;RBTreeNode(const T& data, Color col = Red) //构造函数:_data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(col) //默认新插入节点的颜色为红色{ }
};
//Value决定你是k模型的set、还是KV模型的map
template<class K, class T, class KeyOfT>
class RBTree {
public:typedef RBTreeNode<T> Node;
};
template<class K>
class set{ //K模型
public: private: //set中的key不允许被修改RBTree<K, const K, SetKeyOfT> _t; //红黑树对象};
}
template<class K, class V>
class map { //KV模型
public:private: //map中的key不允许被修改RBTree<K, pair<const K, V>, MapKeyOfT> _t; //红黑树对象};};
1.1.2 KeyOfValue
- KeyOfT : 取出Value对象中的key。
// KeyOfT : 取出Value对象中的key
template<class K, class T, class KeyOfT>
class RBTree { };
struct SetKeyOfT{ const K& operator()(const K& key){return key; //key}
};
struct MapKeyOfT {const K& operator()(const pair<K, V>& kv){return kv.first; //pair中的key}
};
1.2 正向迭代器
1.2.1 构造函数
💡RBTreeIterator(Node* node) ;
RBTreeIterator(Node* node) //构造函数,单参数构造函数支持隐式类型转化:_node(node){ }
- Tips : 单参数构造函数支持隐式类型转换 Node*->iterator 。
1.2.2 begin()+end()
💡iterator begin( ) ;
- 功能:返回红黑树中最左节点(左孩子必为空)的迭代器。
- Tips:set、map对象为非const对象,就调用begin()、end()。
iterator begin() //红黑树最左节点
{ Node* subLeft = _root; while (subLeft && subLeft->_left)subLeft = subLeft->_left;return iterator(subLeft);
}
💡iterator end( ) ;
- 功能:返回最后一个元素的下一个的迭代器(空指针)。
iterator end() //空指针 左闭右开[begin,end)
{return iterator(nullptr);
}
1.2.3 operator++()
/*1.右不为空,下一个为该节点右子树的最左节点 ;* 2.右为空,说明该节点所在的子树已经访问完了,若该节点为父亲的右,说明该父亲所在的子树也访问完了,继续往上找,* 直到该节点为父亲的左,则要访问的下一个节点是它的父亲,即:找祖先里面的孩纸 = 父亲左*/
Self& operator++() //中序 左、根、右
{if (_node->_right) //1{Node* subLeft = _node->_right;while (subLeft->_left)subLeft = subLeft->_left;_node = subLeft;}else //2{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left != cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}
- 中序遍历,左、根、右。
情况一:右不为空,下一个为该节点右子树的最左节点 ;
情况二:右为空,说明该节点所在的子树已经访问完了,若该节点为父亲的右,说明该父亲所在的子树也访问完了,继续往上找,直到该节点为父亲的左,则要访问的下一个节点是它的父亲,即:找祖先里面的孩纸 = 父亲左。
1.2.4 operator–()
/*1.左不为空,下一个为该节点左子树的最右节点 ;* 2.左为空,说明该节点所在的子树已经访问完了,若该节点为父亲的左,说明该父亲所在的子树也访问完了,继续往上找,* 直到该节点为父亲的右,则要访问的下一个节点是它的父亲,即:找祖先里面的孩纸 = 父亲右*/
Self& operator--() //中序 左、根、右 --与++逻辑相反
{if (_node->_left) //1{Node* subRight = _node->_left;while (subRight->_right)subRight = subRight->_right;_node = subRight;}else //2{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right != cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}
1.2.5 operator*()
Ref operator*()
{return _node->_data;
}
1.2.6 operator->()
Ptr operator->() //结构体指针,data为结构体
{return &_node->_data;
}
1.2.7 operator==()
bool operator==(const Self& rb)
{return _node == rb._node;
}
1.2.8 operator!=()
bool operator!=(const Self& rb)
{return _node != rb._node;
}
1.2.9 总代码
template<class T, class Ref, class Ptr>
struct RBTreeIterator //红黑树的正向迭代器
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;RBTreeIterator(Node* node) //构造函数,单参数构造函数支持隐式类型转化:_node(node){ }Ref operator*(){return _node->_data;}Ptr operator->() //结构体指针,data为结构体{return &_node->_data;}/*1.右不为空,下一个为该节点右子树的最左节点 ;* 2.右为空,说明该节点所在的子树已经访问完了,若该节点为父亲的右,说明该父亲所在的子树也访问完了,继续往上找,* 直到该节点为父亲的左,则要访问的下一个节点是它的父亲,即:找祖先里面的孩纸 = 父亲左*/Self& operator++() //中序 左、根、右{if (_node->_right) //1{Node* subLeft = _node->_right;while (subLeft->_left)subLeft = subLeft->_left;_node = subLeft;}else //2{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left != cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}/*1.左不为空,下一个为该节点左子树的最右节点 ;* 2.左为空,说明该节点所在的子树已经访问完了,若该节点为父亲的左,说明该父亲所在的子树也访问完了,继续往上找,* 直到该节点为父亲的右,则要访问的下一个节点是它的父亲,即:找祖先里面的孩纸 = 父亲右*/Self& operator--() //中序 左、根、右 --与++逻辑相反{if (_node->_left) //1{Node* subRight = _node->_left;while (subRight->_right)subRight = subRight->_right;_node = subRight;}else //2{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right != cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}bool operator!=(const Self& rb){return _node != rb._node;}bool operator==(const Self& rb){return _node == rb._node;}
};
1.3 反向迭代器
1.3.1 rbegin()+rend()
💡reverse_iterator rbegin( ) ;
- 功能:返回红黑树中最右节点(右孩子必为空)的迭代器。
reverse_iterator rbegin() //红黑树最右节点,因为此处反向迭代器*,是直接调用正向迭代器* [rbegin,erend)
{Node* subRight = _root;while (subRight && subRight->_right)subRight = subRight->_right;return reverse_iterator(subRight);
}
💡reverse_iterator rend( ) ;
- 功能:返回第一个元素的前一个的迭代器(空指针)。
reverse_iterator rend()
{return reverse_iterator(nullptr);
}
1.3.2 总代码
template<class iterator, class Ref, class Ptr>
struct ReverseIterator //红黑树的反向迭代器——适配器 begin = rend、end = rbegin
{typedef ReverseIterator<iterator, Ref, Ptr> Self; iterator _it; //适配器ReverseIterator(iterator it):_it(it){ }Ref operator*(){return *_it;}Ptr operator->(){return &(operator*()); //}Self& operator++(){--_it;return *this;}Self& operator--(){++_it;return *this;}bool operator==(const Self& rb){return _it == rb._it;}bool operator!=(const Self& rb){return _it != rb._it;}
};
1.4 find()
💡iterator find(const K& key) ;
- 功能:查找。
- 若key在红黑树中,则返回树中与key值相等元素的迭代器,否则,就返回end( )。
iterator find(const K& key) //查找 模板参数K的作用
{KeyOfT kot;Node* cur = _root;while (cur) //先按照二叉搜索树的方式插入{if (kot(cur->_data) < key) //通仿函数对象调用operator()来获取T中的keycur = cur->_right;else if (kot(cur->_data) > key)cur = cur->_left;elsereturn iterator(cur); //找到了}return end(); //找不到
}
1.5 insert()
💡pair<iterator,bool> insert(const T& data) ;
- 功能:向红黑树中插入data。
- insert返回值为pair<iterator, bool>,若key(set的key、map的pair的first)在树中存在,因为搜索树中不能出现重复的键值key,所以pair::first指向在树中与key值相等的迭代器,pair::second为false。若key在树中不存在,pair::first指向在树中新插入元素的迭代器,pair::second为true。insert相当于查找。
pair<iterator, bool> insert(const T& data) //插入
{ //不能使用引用放回,因为返回值作用域为栈区,传值返回KeyOfT kot; //仿函数类创建的对象,对象去调用operator()Node* parent = nullptr;Node* cur = _root;while (cur) //先按照二叉搜索树的方式插入{if (kot(cur->_data) < kot(data)) //通仿函数对象调用operator()来获取T中的key值{parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false); //插入节点已存在,插入失败,返回在树中与该节点值相同的迭代器}}cur = new Node(data); //注意 insertNode* newnode = cur; //非空树,插入成功,因为要做旋转处理,会导致cur值发生改变,需提前将新节点的位置存储起来if (parent == nullptr) //空树{_root = cur;_root->_col = Black; //跟节点为黑return make_pair(iterator(_root), true); //空树,插入成功,返回新插入节点在树中的迭代器}if (kot(parent->_data) < kot(cur->_data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent; //记录当前节点的父亲//更新颜色//插入结束:1.插入节点的父亲是黑色,因为插入前该树就为红黑树 2.情况一处理完后,cur为根节点,且为黑色while (parent && parent->_col == Red){ //爷爷一定存在,因为c为红,p为红,所以p一定不是根节点,且一定有父节点Node* grandfather = parent->_parent;if (parent == grandfather->_left) //旋转需要确定方向{Node* uncle = grandfather->_right;if (uncle && uncle->_col == Red) //情况一:叔叔存在且为红->无方向(p、u为g的任意边,c为p的任一边){ //cur可能为新增节点,也可能一开始为黑色,cur的子树(下一层为红,下一层为新插入节点)在调整过程中将cur由黑变为红parent->_col = uncle->_col = Black; //p、u变为黑,g变为红grandfather->_col = Red;//g可能为根节点(更新结束),也可能为子树(继续向上更新)cur = grandfather;parent = cur->_parent;}else //情况二:叔叔不存在 或者 叔叔存在且为黑{ //叔叔不存在,cur为新增节点 或 cur原来为黑,经子树调整由黑变红if (parent->_left == cur) //左左——右单旋{RotateR(grandfather);parent->_col = Black; //p变为黑,g变为红grandfather->_col = Red;}else //左右——左右单旋 {RotateL(parent);RotateR(grandfather);cur->_col = Black; //c变黑,g变红grandfather->_col = Red;}break; //更新结束:3.旋转+颜色处理后就是红黑树了}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == Red){parent->_col = uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur) //右右——左单旋{RotateL(grandfather);parent->_col = Black;grandfather->_col = Red;}else //右左——右左单旋{RotateR(parent);RotateL(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}}_root->_col = Black; //g为根,颜色变为黑,更新结束return make_pair(iterator(newnode), true); //情况一,插入节点的父亲为黑,插入结束
}
2. Map
2.1 operator[]
💡V& operator[ ](const K& key) ;
- 功能:访问与key相对应的value值。即可读又可写。
- 原理:operator[ ]底层是通过调用insert( )将键值队插入到map中。如果key存在,插入失败,insert返回与map中key值相同元素的迭代器。如果key不存在,插入成功,insert返回在map中新插入元素的迭代器。operator[ ]最后返回与key值相对应的value值的引用。
- operator[ ] 具有插入、查找、插入+修改、查找+修改功能。
V& operator[](const K& key) //功能:查找+修改、插入+修改
{pair<iterator, bool> ret = _t.insert(make_pair(key, V()));return ret.first->second;
}
3. typename作用
- 使用域作用限定符(: : )的两种情况:静态变量、类中typedef的类型。
- 使用typename表示: :后面为类型,不是静态成员
//使用::两种情况:静态变量、类中typedef的类型 typename表示::前面为类型,不是静态成员
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
4. 完整代码
4.1 Map.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1#include"RBTree.h"
#include<string>namespace zzx {template<class K, class V>class map { //KV模型 public:struct MapKeyOfT {const K& operator()(const pair<K, V>& kv){return kv.first; //pair中的key}};//使用::两种情况:静态变量、类中typedef的类型 typename表示::前面为类型,不是静态成员typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器iterator begin() {return _t.begin();}iterator end() {return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}iterator find(const K& key) //查找{return _t.find(key);}pair<iterator, bool> insert(pair<const K, V>& kv) //插入{return _t.insert(kv);}V& operator[](const K& key) //功能:查找+修改、插入+修改{pair<iterator, bool> ret = _t.insert(make_pair(key, V()));return ret.first->second;}private: //map中的key不允许被修改RBTree<K, pair<const K, V>, MapKeyOfT> _t; //红黑树对象};};
4.2 Set.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1#include"RBTree.h"namespace zzx{template<class K>class set{ //K模型public: //仿函数类:比较对象大小,获取对象中的元素—自己手动传递比较逻辑struct SetKeyOfT{ const K& operator()(const K& key){return key; //key}};//使用::两种情况:静态变量、类中typedef的类型 typename表示::后面为类型,不是静态成员typedef typename RBTree<K, const K, SetKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K, const K, SetKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器iterator begin(){return _t.begin();}iterator end(){return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}iterator find(const K& key) //查找{return _t.find(key);}pair<iterator, bool> insert(const K& key) //插入{return _t.insert(key);}private: //set中的key不允许被修改RBTree<K, const K, SetKeyOfT> _t; //红黑树对象};
}
4.3 RBTree.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>using namespace std;enum Color { //枚举,一一列举出事物具有的所有可能Red, //枚举常量,给枚举变量进行赋值Black,
};template<class T>//红黑树的节点
struct RBTreeNode {typedef RBTreeNode<T> Node;//三叉链-》优点:便于查找孩子、父亲节点Node* _left; //该节点的左孩子Node* _right; //该节点的右孩子Node* _parent; //该节点的父亲,便于向上更新T _data;Color _col;RBTreeNode(const T& data, Color col = Red) //构造函数:_data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(col) //默认新插入节点的颜色为红色{ }
};template<class T, class Ref, class Ptr>
struct RBTreeIterator //红黑树的正向迭代器
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;RBTreeIterator(Node* node) //构造函数,单参数构造函数支持隐式类型转化:_node(node){ }Ref operator*(){return _node->_data;}Ptr operator->() //结构体指针,data为结构体{return &_node->_data;}/*1.右不为空,下一个为该节点右子树的最左节点 ;* 2.右为空,说明该节点所在的子树已经访问完了,若该节点为父亲的右,说明该父亲所在的子树也访问完了,继续往上找,* 直到该节点为父亲的左,则要访问的下一个节点是它的父亲,即:找祖先里面的孩纸 = 父亲左*/Self& operator++() //中序 左、根、右{if (_node->_right) //1{Node* subLeft = _node->_right;while (subLeft->_left)subLeft = subLeft->_left;_node = subLeft;}else //2{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left != cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}/*1.左不为空,下一个为该节点左子树的最右节点 ;* 2.左为空,说明该节点所在的子树已经访问完了,若该节点为父亲的左,说明该父亲所在的子树也访问完了,继续往上找,* 直到该节点为父亲的右,则要访问的下一个节点是它的父亲,即:找祖先里面的孩纸 = 父亲右*/Self& operator--() //中序 左、根、右 --与++逻辑相反{if (_node->_left) //1{Node* subRight = _node->_left;while (subRight->_right)subRight = subRight->_right;_node = subRight;}else //2{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right != cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}bool operator!=(const Self& rb){return _node != rb._node;}bool operator==(const Self& rb){return _node == rb._node;}
};template<class iterator, class Ref, class Ptr>
struct ReverseIterator //红黑树的反向迭代器——适配器 begin = rend、end = rbegin
{typedef ReverseIterator<iterator, Ref, Ptr> Self; iterator _it; //适配器ReverseIterator(iterator it):_it(it){ }Ref operator*(){return *_it;}Ptr operator->(){return &(operator*()); //}Self& operator++(){--_it;return *this;}Self& operator--(){++_it;return *this;}bool operator==(const Self& rb){return _it == rb._it;}bool operator!=(const Self& rb){return _it != rb._it;}
};//红黑树的模板参数:T决定你是k模型的set、还是KV模型的map ; KeyOfT:取出T对象中的key ; pair比较:先比较first,在比较second
template<class K, class T, class KeyOfT>
class RBTree {
public:typedef RBTreeNode<T> Node;//正向迭代器typedef RBTreeIterator<T, T&, T*> iterator; //普通迭代器typedef RBTreeIterator<T,const T&, const T*> const_iterator; //const迭代器//反向迭代器typedef ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;iterator begin() //红黑树最左节点{ Node* subLeft = _root; while (subLeft && subLeft->_left)subLeft = subLeft->_left;return iterator(subLeft);}iterator end() //空指针 左闭右开[begin,end){return iterator(nullptr);}const_iterator begin()const {Node* subLeft = _root;while (subLeft && subLeft->_left)subLeft = subLeft->_left;return const_iterator(subLeft);}const_iterator end()const{return const_iterator(nullptr);}reverse_iterator rbegin() //红黑树最右节点,因为此处反向迭代器*,是直接调用正向迭代器* [rbegin,erend){Node* subRight = _root;while (subRight && subRight->_right)subRight = subRight->_right;return reverse_iterator(subRight);}reverse_iterator rend() {return reverse_iterator(nullptr);}const_reverse_iterator rbegin()const{Node* subRight = _root;while (subRight && subRight->_right)subRight = subRight->_right;return const_reverse_iterator(subRight);}const_reverse_iterator rend()const{return const_reverse_iterator(nullptr);}iterator find(const K& key) //查找 模板参数K的作用{KeyOfT kot;Node* cur = _root;while (cur) //先按照二叉搜索树的方式插入{if (kot(cur->_data) < key) //通仿函数对象调用operator()来获取T中的keycur = cur->_right;else if (kot(cur->_data) > key)cur = cur->_left;elsereturn iterator(cur); //找到了}return end(); //找不到}void RotateL(Node* parent) //右右—左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pphead = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (pphead->_left == parent)pphead->_left = subR;elsepphead->_right = subR;subR->_parent = pphead;}}void RotateR(Node* parent) //左左—右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pphead = parent->_parent;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pphead->_left == parent)pphead->_left = subL;elsepphead->_right = subL;subL->_parent = pphead;}}void RotateRL(Node* parent) //右左—先右旋再左旋{Node* subR = parent->_right;Node* subRL = subR->_left;RotateR(subR);RotateL(parent);}void RotateLR(Node* parent) //左右—先左旋再右旋{Node* subL = parent->_left;Node* subLR = subL->_right;RotateL(subL);RotateR(parent);}pair<iterator, bool> insert(const T& data) //插入 { //不能使用引用放回,因为返回值作用域为栈区,传值返回KeyOfT kot; //仿函数类创建的对象,对象去调用operator()Node* parent = nullptr;Node* cur = _root;while (cur) //先按照二叉搜索树的方式插入{if (kot(cur->_data) < kot(data)) //通仿函数对象调用operator()来获取T中的key值{parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false); //插入节点已存在,插入失败,返回在树中与该节点值相同的迭代器}}cur = new Node(data); //注意 insertNode* newnode = cur; //非空树,插入成功,因为要做旋转处理,会导致cur值发生改变,需提前将新节点的位置存储起来if (parent == nullptr) //空树{_root = cur;_root->_col = Black; //跟节点为黑return make_pair(iterator(_root), true); //空树,插入成功,返回新插入节点在树中的迭代器}if (kot(parent->_data) < kot(cur->_data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent; //记录当前节点的父亲//更新颜色//插入结束:1.插入节点的父亲是黑色,因为插入前该树就为红黑树 2.情况一处理完后,cur为根节点,且为黑色while (parent && parent->_col == Red){ //爷爷一定存在,因为c为红,p为红,所以p一定不是根节点,且一定有父节点Node* grandfather = parent->_parent;if (parent == grandfather->_left) //旋转需要确定方向{Node* uncle = grandfather->_right;if (uncle && uncle->_col == Red) //情况一:叔叔存在且为红->无方向(p、u为g的任意边,c为p的任一边){ //cur可能为新增节点,也可能一开始为黑色,cur的子树(下一层为红,下一层为新插入节点)在调整过程中将cur由黑变为红parent->_col = uncle->_col = Black; //p、u变为黑,g变为红grandfather->_col = Red;//g可能为根节点(更新结束),也可能为子树(继续向上更新)cur = grandfather;parent = cur->_parent;}else //情况二:叔叔不存在 或者 叔叔存在且为黑{ //叔叔不存在,cur为新增节点 或 cur原来为黑,经子树调整由黑变红if (parent->_left == cur) //左左——右单旋{RotateR(grandfather);parent->_col = Black; //p变为黑,g变为红grandfather->_col = Red;}else //左右——左右单旋 {RotateL(parent);RotateR(grandfather);cur->_col = Black; //c变黑,g变红grandfather->_col = Red;}break; //更新结束:3.旋转+颜色处理后就是红黑树了}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == Red){parent->_col = uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur) //右右——左单旋{RotateL(grandfather);parent->_col = Black;grandfather->_col = Red;}else //右左——右左单旋{RotateR(parent);RotateL(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}}_root->_col = Black; //g为根,颜色变为黑,更新结束return make_pair(iterator(newnode), true); //情况一,插入节点的父亲为黑,插入结束}private:Node* _root = nullptr;
};
相关文章:
【C++】用红黑树封装map、set
用红黑树封装map、set 1. 红黑树1.1 模板参数的控制1.1.1 Value1.1.2 KeyOfValue 1.2 正向迭代器1.2.1 构造函数1.2.2 begin()end()1.2.3 operator()1.2.4 operator--()1.2.5 operator*()1.2.6 operator->()1.2.7 operator()1.2.8 operator!()1.2.9 总代码 1.3 反向迭代器1.…...
【中颖】SH79F9202 串口通信
头文件 uart.h #ifndef UART_H #define UART_H#include "SH79F9202.h" #include "LCD.h" #include "timer2.h" #include "timer5.h" #include "cpu.h" #include "key.h" #include "io.h" #include &qu…...
IDEA创建Maven项目
IDEA创建Maven项目 第一步:创建新项目 或者 第二步:创建maven模块 前提条件: File>>Settings,检查自己的maven是否已经安装配置好 创建maven模块 其中Archetype一般选择如下 点击创建后生成如下 需要在main目录下创…...
[每周一更]-(第100期):介绍 goctl自动生成代码
在自己组件库中,由于部分设计会存在重复引用各个模板的文件,并且基础架构中需要基础模块内容,就想到自动生成代码模板,刚好之前有使用过goctl,以下就简单描述下gozero中goctl场景和逻辑,后续自己借鉴将自…...
碳素钢化学成分分析 螺纹钢材质鉴定 钢材维氏硬度检测
碳素钢的品种主要有圆钢、扁钢、方钢等。经冷、热加工后钢材的表面不得有裂缝、结疤、夹杂、折叠和发纹等缺陷。尺寸和允许公差必须符合相应品种国家标准的要求。 具体分类、按化学成分分类 : 碳素钢按化学成分(即以含碳量)可分为低碳钢、中…...
C++ list链表的使用和简单模拟实现
目录 前言 1. list的简介 2.list讲解和模拟实现 2.1 默认构造函数和push_back函数 2.2 迭代器实现 2.2.1 非const正向迭代器 2.2.2 const正向迭代器 2.2.3 反向迭代器 2.3 插入删除函数 2.3.1 insert和erase 2.3.2 push_back pop_back push_front pop_front 2.4 构…...
dependencies?devDependencies?peerDependencies
之前使用的npm包中,我用到了sass包。我当时没有在packagejson中添加依赖项,而是另外install的。这就引起了我的一个思考 初步想法: 我的npm包需要使用sass,那么我应该放在dependencies中,当使用的时候会直接下载 问题…...
在LUAT中使用MQTT客户端,游戏脚本,办公脚本自动操作
本文将介绍在LUAT中工程化使用MQTT客户端的方法及注意事项。实验平台为合宙AIR724UG,其固件版本为Luat_V4001_RDA8910_FLOAT_TMP。 面向对象 使用middleclass库为脚本提供基础面向对象支持,将此repo中的middleclass.lua文件添加到项目中即可使用。middl…...
如何解决maven中snapshot相关jar无法拉取问题
Maven中的SNAPSHOT版本是指正在开发中的版本,这些版本可能会频繁地更新。在使用Maven构建项目时,有时会遇到无法拉取SNAPSHOT相关jar的问题。以下是几种常见的解决方案: 1. 检查Maven配置文件(settings.xml) 确保你的M…...
类似crossover的容器软件有哪些 除了crossover还有什么 Mac虚拟机替代品
CrossOver是Mac用来运行exe文件的一款软件,但是并不是所有的exe文件CrossOver都支持运行。想要在Mac上运行exe文件的方法并不是只有使用CrossOver这一种,那么有没有类似的软件也可以实现exe文件在Mac上运行呢? CrossOver类似软件有哪些 1、Pl…...
以sqlilabs靶场为例,讲解SQL注入攻击原理【54-65关】
【Less-54】 与前面的题目不同是,这里只能提交10次,一旦提交超过十次,数据会重新刷新,所有的步骤需要重来一次。 解题步骤: 根据测试,使用的是单引号闭合。 # 判断字段的数量 ?id1 order by 3 -- aaa# …...
详解 Flink 的时间语义和 watermark
一、Flink 时间语义类型 Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink 通过时间戳分配器访问事件时间戳Ingestion Time :是数据进入 Flink…...
Unreal Engine项目结构与关卡设置详解
引言 Unreal Engine 是一款功能强大的游戏引擎,为开发者提供了丰富的工具来创建和管理游戏项目。本文将详细介绍一个基本的 Unreal Engine 项目结构,并讲解如何在 Unreal 编辑器中进行关卡设置与操作。 Unreal Engine 项目结构 一个基本的 Unreal Eng…...
Access数据中的SQL偏移注入
使用场景: 目标数据表的字段较多,无法一一获取的时候,尝试使用偏移注入的方式实现SQL注入。 原理: 例如:一个表有6个字段,而你想获取的目标表admin的字段不知道,此时可以使用联合查询的方式获…...
Unity 编辑器扩展,获取目录下所有的预制件
先看演示效果 实现方案 1创建几个用于测试的cube 2,创建一个Editor脚本 3,编写脚本内容 附上源码 using UnityEditor; using UnityEngine;public class GetPrefeb : EditorWindow {private string folderPath "Assets/Resources"; // 指定预…...
【Python】解决Python报错:ValueError: not enough values to unpack (expected 2, got 1)
文章目录 引言1. 错误详解2. 常见的出错场景2.1 函数返回值解包2.2 遍历含有不同长度元组的列表 3. 解决方案3.1 检查和调整返回值3.2 安全的解包操作 4. 预防措施4.1 使用异常处理4.2 单元测试 结语 引言 在Python编程中,ValueError 是一个常见的异常类…...
政安晨【零基础玩转各类开源AI项目】解析开源:gradio:改进真实虚拟试穿的扩散模型
政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 收录专栏: 零基础玩转各类开源AI项目 希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正! Gradio 是一个开源 Python 软件包,可以让你…...
深入解读Prometheus Adapter:云原生监控的核心组件
一、引言 Prometheus Adapter的背景与重要性 在现代的云原生架构中,微服务和容器化技术得到了广泛的应用。这些技术带来了系统灵活性和扩展性的提升,但同时也增加了系统监控和管理的复杂度。Prometheus作为一款开源的监控系统,因其强大的指标…...
【计算机视觉】数字图像处理基础:以像素为单位的图像基本运算(点运算、代数运算、逻辑运算、几何运算、插值)
0、前言 在上篇文章中,我们对什么是数字图像、以及数字图像的组成(离散的像素点)进行了讲解🔗【计算机视觉】数字图像处理基础知识:模拟和数字图像、采样量化、像素的基本关系、灰度直方图、图像的分类。 我们知道&a…...
Spring Boot整合WebSocket和Redis实现直播间在线人数统计功能
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…...
uniapp自定义的下面导航
uniapp自定义的下面导航 看看效果图片吧 文章目录 uniapp自定义的下面导航 看看效果图片吧 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6aa0e964741d4dd3a58f4e86c4bf3247.png) 前言一、写组件、我这里就没有写组件了直接写了一个页面?总结 前言 在…...
【Python】selenium使用find_element时解决【StaleElementReferenceException】问题的方法
StaleElementReferenceException 是 Selenium WebDriver 中的一种异常,通常在元素与当前页面的状态不同步时抛出,比如页面已经刷新或导航到另一个页面,但是尝试操作的元素引用仍然是旧页面上的元素。 以下是一些解决 StaleElementReferenceE…...
Apache IoTDB 分布式架构三部曲(三)副本与共识算法
IoTDB 首创并应用的共识协议统一框架,为用户提供了灵活选择不同共识算法的可能性。 对于一个分布式集群而言,为了使得海量数据场景下集群能够横向扩展,集群需要按照一定的规则将全部数据分成多个子集存储在不同的节点上,从而能够更…...
数据挖掘--聚类分析:基本概念和方法
数据挖掘--引论 数据挖掘--认识数据 数据挖掘--数据预处理 数据挖掘--数据仓库与联机分析处理 数据挖掘--挖掘频繁模式、关联和相关性:基本概念和方法 数据挖掘--分类 数据挖掘--聚类分析:基本概念和方法 聚类分析 聚类分析是把一个数据对象&…...
APP单页分发源码下载安卓苹果自动识别apk描述文件免签自动安装
下载地址:APP单页分发源码下载安卓苹果自动识别apk描述文件免签自动安装...
golang定时器使用示例
1.定时器创建与停止 //定时器使用t1 : time.NewTimer(2 * time.Second)<-t1.Cfmt.Println("timer1 fired")t2 : time.NewTimer(5 * time.Second)go func() {fmt.Println("go协程处理中,等待5秒后输出...")<-t2.Cfmt.Println("timer2 fired&quo…...
[FSCTF 2023]Tea_apk
得到密文和密钥 import base64 from ctypes import c_uint32import libnumDELTA 0x9E3779B9def decrypt(v, n, k):rounds 6 int(52 / n)sum c_uint32(rounds * DELTA)y v[0].valuewhile rounds > 0:e (sum.value >> 2) & 3p n - 1while p > 0:z v[p …...
分享一个用python写的本地WIFI密码查看器
本章教程,主要分享一个本地wifi密码查看器,用python实现的,感兴趣的可以试一试。 具体代码 import subprocess # 导入 subprocess 模块,用于执行系统命令 import tkinter as tk # 导入 tkinter 模块,用于创建图形用…...
【SkyWalking】启用apm-trace-ignore-plugin追踪忽略插件
背景 使用Agent采集追踪数据的时候,想排除某些路径,比如健康检查等,这样可以减少上报的数据,也可以去除一些不必要的干扰数据。 加载插件 在agent/optional-plugins目录中有个apm-trace-ignore-plugin-${version}.jar插件&…...
独立游戏之路 -- 获取OAID提升广告收益
Unity 之 获取手机:OAID、IMEI、ClientId、GUID 前言一、Oaid 介绍1.1 Oaid 说明1.2 移动安全联盟(MSA) 二、站在巨人的肩膀上2.1 本文实现参考2.2 本文实现效果2.3 本文相关插件 三、Unity 中获取Oaid3.1 查看实现源码3.2 工程配置3.3 代码实现3.4 场景搭建 四、总…...
英文网站建设 江门/百度搜索关键词热度
我安装的是Ubuntu18.04系统,每次双击sh文件都是用vim,我还得切换到命令行去执行;这就有点不方便了。 解决办法:点击Preferences设置 切换到Behavior,选择Run them 最后到这个需要执行的文件属性里去,勾选Allow executing file as …...
网站关键词做的越多越好吗/南宁seo网站排名优化公司
ApiOperation(value "说明", httpMethod "get", notes "信息")ApiOperation.httpMethod.get改成httpMethod.GET即可...
免费b2b网站排名/360免费建站
hp m425dn打印机ADC进纸器复印或者扫描全黑。平板复印文档正常。解决方法:设置-服务-恢复默认值后解决。转载于:https://blog.51cto.com/tianhui/1695334...
培训页面设计师/西安seo外包行者seo06
Oracle读取和修改数据块的过程Oracle数据库处理SQL都会经过三个过程:解析(parse)、执行(exec)、返回结果(fetch)。为了便于理解,我们首先省略的SQL的处理过程,而直接介绍数据块的读取或修改过程。物理读和逻辑读概念1.对要访问的块地址做HASHHASH(FILE#,…...
php是做网站的吗/免费建站模板
第1关:python数据库编程之创建数据库 本关任务:使用 pymysql 创建一个名为 mydb 的数据库。 import pymysql"""需求:创建一个名为 mydb 的数据库 """ if __name__ == __main__:# **********begin********** ## 获取连接conn = pymysql.conn…...
中央农村工作会议内容和精神体会/东莞seo优化团队
前两天有个小伙伴在后台留言,最近的面试越来越难了,尤其是技术面,考察得越来越细,越来越底层,庆幸的是最终顺利找到了工作。 一般技术面试官都会通过自己的方式去考察程序员的技术功底与基础理论知识 比如果这样的问题…...