用一颗红黑树同时封装出map和set
目录
1. 红黑树源代码
2. 红黑树模版参数的控制
3. 红黑树节点当中存储的数据
4. 模板参数中仿函数的增加
5. 正向迭代器的实现
6. set模拟实现
7. map的模拟实现
8. 封装后的代码
8.1 红黑树的代码
8.2 正向迭代器的代码
8.3 set的代码
8.4 map的代码
1. 红黑树源代码
#pragma onceenum Colour
{RED,BLACK
};//定义的是红黑树中节点类型
template<class K, class V>
struct RBTreeNode
{RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;
};//定义红黑树
template<class K, class V>
class RBTree
{public:typedef RBTreeNode<K, V> Node;// 获取红黑树最左侧节点Node* LeftMost(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return cur;}// 获取红黑树最右侧节点Node* RightMost(){Node* cur = _root;while (cur && cur->_right){cur = cur->_right;}return cur;}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){//插入元素大于当前元素就往右走if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}//插入元素小于当前元素就往左走else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{//在树中找到了kv(插入失败)return false;}}//新建节点cur = new Node(kv);//如果kv大于父亲就插入到父亲的右边if (parent->_kv.first < kv.first){parent->_right = cur;}else{//如果kv小于父亲就插入到父亲的左边parent->_left = cur;}cur->_parent = parent;//判断是否需要调整//父亲节点颜色是红的,那就说明其一定还有父亲//父亲存在才需要继续向上调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//如果父亲在爷爷的左边if (parent == grandfather->_left){//那叔叔如果存在,那就一定在爷爷的右边Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// g// p u//这里走的是情况1parent->_col = uncle->_col = BLACK;//最后我们会统一把根节点变黑,//所以就不怕grandfather是根节点而我们还让其变红grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//u存在且为黑或不存在 --> 旋转+变色// g// p u//c//单旋if (parent->_left == cur){//右旋RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{// g// p u// c//双选//先左旋在右旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}}else{//父亲在爷爷的右边u// g// u p// c//这里走的也是情况1Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//uncle存在为黑或者不存在走情况2和3if (parent->_right == cur){// g// u p// c//左旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// c//先右旋在左旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}}}//根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)_root->_col = BLACK;return true;}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}//右旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;}//判断是否为红黑树bool ISRBTree(){if (_root == nullptr){return true;}int BlackCount = 0;Node* cur = _root;while (cur){//我们获得最左边路径黑色节点的个数if (cur->_col == BLACK)BlackCount++;cur = cur->_left;}int count = 0;return _ISRBTree(_root, count, BlackCount);}//判断是否为红黑树的子函数bool _ISRBTree(Node* root, int count, int BlackCount){if (root == nullptr){if (count != BlackCount)return false;elsereturn true;}Node* cur = root;if (cur->_col == BLACK)count++;//如果当前节点是红色,那它一定有父节点if (cur->_col == RED && cur->_parent->_col == RED)return false;return _ISRBTree(root->_left, count, BlackCount) && _ISRBTree(root->_right, count, BlackCount);}void InOrder(){_InOrder(_root);cout << endl;}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first;cout << endl;_InOrder(root->_right);}Node* _root = nullptr;
};
2. 红黑树模版参数的控制
我们都知道,set是K模型的容器,而map是KV模型的容器,那我们如何用一棵KV模型的红黑树同时实现map和set呢?
这里我们就需要控制map和set传入底层红黑树的模板参数,为了与原红黑树的模板参数进行区分,我们将红黑树第二个模板参数的名字改为T。
template<class K, class T>
class RBTree
T模板参数可能只是键值Key,也可能是由Key和Value共同构成的键值对。如果是set容器,那么它传入底层红黑树的模板参数就是Key和Key:
template<class K>
class set
{
public://...
private:RBTree<K, K> _t;
};
但如果是map容器,那么它传入底层红黑树的模板参数就是Key以及Key和Value构成的键值对:
template<class K, class V>
class map
{
public://...
private:RBTree<K, pair<K, V>> _t;
};
那能不能不要红黑树的第一个模板参数,只保留第二个模板参数呢?
乍眼一看好像是可以的,因为此时红黑树的第二个模板参数当中也是有键值Key的,但实际上红黑树的第一个模板参数是不能省略的。
对于set容器来说,省略红黑树的第一个参数当然没问题,因为set传入红黑树的第二个参数与第一个参数是一样的。但是对于map容器来说就不行了,因为map容器所提供的接口当中有些是只要求给出键值Key的,比如find和erase(通过Key来在红黑树中查找节点或者删除节点)。
3. 红黑树节点当中存储的数据
现在红黑树的模板参数变成了K和T,那么红黑树结点当中存储的应该是什么呢?
前面说到,由于上层容器的不同,底层红黑树当中的K和T也是不同的:
- set容器:K和T都代表键值Key。
- map容器:K代表键值Key,T代表由Key和Value构成的键值对。
对于set容器来说,底层红黑树结点当中存储K和T都是一样的,但是对于map容器来说,底层红黑树就只能存储T了。由于底层红黑树并不知道上层容器到底是map还是set,因此红黑树的结点当中直接存储T就行了。
这样一来,当上层容器是set的时候,结点当中存储的是键值Key;当上层容器是map的时候,结点当中存储的就是<Key, Value>键值对。
更改后代码如下:
enum Colour
{RED,BLACK
};//定义的是红黑树中节点类型
template<class T>
struct RBTreeNode
{RBTreeNode(const T& date):_left(nullptr), _right(nullptr), _parent(nullptr), _date(date), _col(RED){}RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _date;Colour _col;
};
4. 模板参数中仿函数的增加
现在由于结点当中存储的是T,这个T可能是Key,也可能是<Key, Value>键值对。那么当我们需要进行结点的键值比较时,应该如何获取结点的键值呢?
当上层容器是set的时候T就是键值Key,直接用T进行比较即可,但当上层容器是map的时候就不行了,此时我们需要从<Key, Value>键值对当中取出键值Key后,再用Key值进行比较。
因此,上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key,这样一来,当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了。
仿函数,就是使一个类的使用看上去像一个函数。其实现就是类中实现一个
operator()
,这个类就有了类似函数的行为,就是一个仿函数类了。
template<class K, class V>
class map
{//仿函数struct MapKeyOfT{const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key{return kv.first;}};
public://...
private:RBTree<K, pair<K, V>, MapKeyOfT> _t;
};
但是对于底层红黑树来说,它并不知道上层容器是map还是set,因此当需要进行两个结点键值的比较时,底层红黑树都会通过传入的仿函数来获取键值Key,进而进行两个结点键值的比较。
因此,set容器也需要向底层红黑树传入一个仿函数,虽然这个仿函数单独看起来没什么用,但却是必不可少的。(毕竟set和map是都是通过传入的模版参数来构造红黑树,因为map需要向红黑树传递仿函数,因此set也被迫需要)
template<class K>
class set
{//仿函数struct SetKeyOfT{const K& operator()(const K& key) //返回键值Key{return key;}};
public://...
private:RBTree<K, K, SetKeyOfT> _t;
};
这样一来,当底层红黑树需要进行两个结点之间键值的比较时,都会通过传入的仿函数来获取相应结点的键值,然后再进行比较,下面以红黑树的查找函数为例:
Iterator* Find(const K& key)
{KeyOfT kot;Node* cur = _root;while (cur){//取cur->_date的key值//如果是pair那就是其firstif (kot(cur->_date) < key){cur = cur->_right;}else if (kot(cur->_date) > key){cur = cur->_left;}else{//这里返回迭代器需要传入root//是为了End(),这个我们后面再说//这里找到了返回当前位置的迭代器return Iterator(cur, _root);}}//找不到返回指向nullptr的迭代器return Iterator(nullptr,_root);
}
注意: 所有进行结点键值比较的地方,均需要通过仿函数获取对应结点的键值后再进行键值的比较。
5. 正向迭代器的实现
红黑树的正向迭代器实际上就是对结点指针进行了封装,因此在正向迭代器当中实际上就只有一个成员变量,那就是正向迭代器所封装结点的指针
但由于如果是迭代器是从End()开始指向,然后减减进行逆向遍历,而此时End()返回的是指向nullptr位置的迭代器,因此如果只有一个节点指针我们是无法找到红黑树中最右的节点(最右节点存最大值)
因此我们这边在实现迭代器时,我们成员变量有2个,一个是保存指向树中节点的指针(能找到的情况下),另一个就是保存能指向根节点的指针(但保存能指向根节点的指针有可能旋转的时候会导致迭代器失效,这个我们后面再讲,现在大家可以先不考虑这个点)
template<class T, class Ref, class Ptr>
class RBTreeIterator
{//节点的类型,这里迭代器就是对节点进行封装typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;private:Node* _node;Node* _root;
};
因此,我们通过2个结点的指针便可以构造出一个正向迭代器。
RBTreeIterator(Node* node, Node* root):_node(node), _root(root)
{
}
当对正向迭代器进行解引用操作时,我们直接返回对应结点数据的引用即可。
Ref operator*()
{return _node->_date;
}
当对正向迭代器进行->
操作时,我们直接返回对应结点数据的指针即可。
Ptr operator->()
{return &_node->_data; //返回结点数据的指针
}
当然,正向迭代器当中至少还需要重载==
和!=
运算符,实现时直接判断两个迭代器所封装的结点是否是同一个即可。
//判断两个正向迭代器是否不同
bool operator!=(const Self& s) const
{return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//判断两个正向迭代器是否相同
bool operator==(const Self& s) const
{return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
红黑树正向迭代器实现时,真正的难点实际上
++
和--
运算符的重载。
实现红黑树的正向迭代器时,一个结点的正向迭代器进行
++
操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点。
首先我们以局部的情况来考虑
首先,假如it走到了13这个节点的位置,那此时右不为空,所以++it,我们因该走到右子树当中的最左节点(这里说的最左节点,就是中序遍历最左节点),而当15走完时右为空,那如果cur节点是在父亲的左边,那++,就走到父亲就停下了,如图:15是在17的左边,所以++it(it没++前指向15这个位置)迭代器就走到指向17这个节点的位置
而如果it指向的是6这个节点,我们++it,此时6是在父亲的右边,那就说明父亲已经走过了,因为中序遍历是左根右,只有访问完1才会走到6,因此如果cur是父亲的右边,那就需要继续向上判断,直到找到cur是父亲的左边才停下来,或者走到parent为空
总结:
- 如果当前结点的右子树不为空,则
++
操作后应该找到其右子树当中的最左结点。 - 如果当前结点的右子树为空,则
++
操作后应该在该结点的祖先结点中,找到孩子不在父亲右的祖先。
代码如下:
Self operator++()
{Node* cur = _node;//如果右边不为空,那就找右边中序(最左边)if (cur->_right){Node* leftMost = cur->_right;while (leftMost->_left){leftMost = leftMost->_left;}_node = leftMost;}else{Node* parent = cur->_parent;//右为空有两种情况//下面这种情况就是一路找祖先parent->_right == curwhile (parent && parent->_right == cur){cur = parent;parent = parent->_parent;}cur = parent;_node = cur;}return *this;
}
实现红黑树的正向迭代器时,一个结点的正向迭代器进行--
操作后,应该根据红黑树中序遍历的序列找到当前结点的前一个结点。
具体逻辑如下:
首先,当it走到了13这个位置,那就说明13的右子树全都已经遍历完了,那如果13的左子树不为空,--it,就要找其左子树中的最右节点,如果如所示,如果it指向13,那--it迭代器就因该走到11这个节点。
那如果左子树为空,就比如现在it指向的是11这个节点,因为11是在8的右边,因此--it迭代器就因该指向8,也就是如果左子树为空,并且cur是在parent的右边,那--it,迭代器就指向parent即可。
那如果左子树为空,cur是在parent的左边,那要怎么办?那我们就要一直向上寻找孩子在父亲右边的祖先,如图:如果it指向的是15,那我们向上找祖先,我们发现当cur是17的时候parent是13,并且cur是在父亲的右边,因此--it,我们就让迭代器指向13节点即可。那如果it是在1这个节点,其parent在8,我们会发现如果一直向上找,当cur为13的时候,parent就是nullptr,那就说明我们整颗树都已经遍历完成了。
总结:
- 如果当前结点的左子树不为空,则
--
操作后应该找到其左子树当中的最右结点。 - 如果当前结点的左子树为空,则
--
操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。
代码如下:
Self operator--()
{if (_node == nullptr){Node* cur = _root;while (cur->_right){cur = cur->_right;}_node = cur;}else if (_node->_left){Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;
}
我们这个代码还而外判断了当_node等于nullptr的时候,因为要满足下面这种场景
因为End我们是用nullptr来构造的,因此当出现了it从指向end开始向前遍历的场景我们就需要通过传入的_root来找到树中的最右节点。
而之前我们说如果迭代器成员变量中加上了_root在旋转的过程中可能会出现迭代器失效的问题是:我们在插入或者寻找节点的时候返回的是迭代器,而如果出现旋转可能整颗树的根节点会改变,而此时我们寻找或者插入时返回的迭代器中_root就会失效(保存的根节点改变了,不是最新的根节点)
正向迭代器实现后,我们需要在红黑树的实现当中进行迭代器类型的typedef。需要注意的是,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在public区域进行typedef。
然后在红黑树当中实现成员函数begin和end:
- begin函数返回中序序列当中第一个结点的正向迭代器,即最左结点。
- end函数返回中序序列当中最后一个结点下一个位置的正向迭代器,这里直接用空指针构造一个正向迭代器。
//定义红黑树
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;Iterator Begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return Iterator(cur, _root);}Const_Iterator End() const{return Const_Iterator(nullptr, _root);}Const_Iterator Begin()const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return Iterator(cur, _root);}Iterator End(){return Iterator(nullptr, _root);}
}
6. set模拟实现
完成上述操作后,set的模拟实现也就很简单了,其中的成员函数都是调用底层红黑树的对应接口就行了,只不过我这里要补充一个关于iterator和const_iterator的点
template <class K>
class set
{struct SetKeyOfT{const K& operator()(const K& k){return k;}};
public:typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::Const_Iterator const_iterator;pair<iterator, bool> insert(const K& key){return _t.Insert(key);}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin()const{return _t.Begin();}const_iterator end()const{return _t.End();}iterator find(const K& key) {_t.Find(key);}private:RBTree<K, const K, SetKeyOfT> _t;
};
首先set和map我们都不希望修改元素的key值,而set在K前面加const进行修饰传给红黑树的模板参数,就能使得我们节点中的元素无法被修改。
注意点:
我们在定义迭代器的时候,我们需要再第二个参数K前面加上const,我们在使用的时候可以把_t前面的模板参数直接复制上去就可以了。那如果在声明定义的时候我们不加const,那我们定义出来的迭代器不是_t中的,因为类型不匹配,那生成红黑树也不同。
而这里我重点说下通过我们传入的模板参数,在_t中生成的迭代器是怎样的(map有类似)
首先我们要了解下面的生成的迭代器我们需要一个共识
如下图所示
其实typeid().name()打印出来有些时候也不真实
是这样子的,typeid().name的方式打印出来的类型非常的简单和粗糙,不能作为真实类型的依据,就简单看看就好了,并且精确类型的打印,标准库中没有专门提供,所以一般来说,后期如果有需要,是会通过下载boost库的
因为在boost库中,有一个boost::typeindex库,他这个就可以做到非常精确的打印因此我们就以下图为准
我们可以看见const int和const const int是同一类型,因此我们继续往下看
7. map的模拟实现
map的模拟实现也是相同的道理,其成员函数也是调用底层红黑树的对应接口,但对于map来说,我们是希望对pair<K,V>中的first值不能修改,但是second是可以修改的,并且在实现map的时候我们还需要额外实现[]
运算符的重载函数。
class map
{struct MapKeyOfT{const K& operator()(const pair<K, V>& k){return k.first;}};
public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Const_Iterator const_iterator;pair<iterator, bool> insert(const pair<K, V>& T){return _t.Insert(T);}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}V& operator[](const K& k){pair<iterator, bool> ret = _t.Insert({k, V()});return ret.first->second;}iterator find(const K& key){return _t.Find(key);}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
8. 封装后的代码
虽然封装过程已经阐述完毕了,但在代码更改过程中还是有许多细节的,下面给出完整封装后的代码。
8.1 红黑树的代码
#pragma onceenum Colour
{RED,BLACK
};//定义的是红黑树中节点类型
template<class T>
struct RBTreeNode
{RBTreeNode(const T& date):_left(nullptr), _right(nullptr), _parent(nullptr), _date(date), _col(RED){}RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _date;Colour _col;
};template<class T, class Ref, class Ptr>
class RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;
public:RBTreeIterator(Node* node, Node* root):_node(node), _root(root){}//判断两个正向迭代器是否不同bool operator!=(const Self& s) const{return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个}//判断两个正向迭代器是否相同bool operator==(const Self& s) const{return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个}Ref operator*(){return _node->_date;}Ptr operator->(){return &(_node->_date);}Self operator++(){Node* cur = _node;//如果右边不为空,那就找右边中序(最左边)if (cur->_right){Node* leftMost = cur->_right;while (leftMost->_left){leftMost = leftMost->_left;}_node = leftMost;}else{Node* parent = cur->_parent;//右为空有两种情况//下面这种情况就是一路找祖先parent->_right == curwhile (parent && parent->_right == cur){cur = parent;parent = parent->_parent;}cur = parent;_node = cur;}return *this;}Self operator--(){if (_node == nullptr){Node* cur = _root;while (cur->_right){cur = cur->_right;}_node = cur;}else if (_node->_left){Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const Self s1){return _node != s1._node;}private:Node* _node;Node* _root;
};//定义红黑树
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;Iterator Begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return Iterator(cur, _root);}Const_Iterator End() const {return Const_Iterator(nullptr, _root);}Const_Iterator Begin()const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return Iterator(cur, _root);}Iterator End(){return Iterator(nullptr, _root);}pair<Iterator, bool> Insert(const T& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return { Iterator(_root, _root),true };}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(cur->_date) < kot(kv)){parent = cur;cur = cur->_right;}else if (kot(cur->_date) > kot(kv)){parent = cur;cur = cur->_left;}else{return { Iterator(cur,_root),false };}}cur = new Node(kv);Node* Newnode = cur;if (kot(parent->_date) < kot(kv)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//判断是否需要调整//父亲节点颜色是红的,那就说明其一定还有父亲//父亲存在才需要继续向上调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// g// p u//这里走的是情况1parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//u存在且为黑或不存在 --> 旋转+变色// g// p u//c//单旋if (parent->_left == cur){//右旋RotateR(grandfather);grandfather->_col = cur->_col = RED;parent->_col = BLACK;}else{// g// p u// c//双选//先左旋在右旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}}else{//父亲在爷爷的右边u// g// u p// c//这里走的也是情况1Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//uncle存在为黑或者不存在走情况2和3if (parent->_right == cur){// g// u p// c//左旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// c//先右旋在左旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}}}_root->_col = BLACK;return { Iterator(Newnode,_root),true };}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}//右旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}Iterator* Find(const K& key)
{KeyOfT kot;Node* cur = _root;while (cur){//取cur->_date的key值//如果是pair那就是其firstif (kot(cur->_date) < key){cur = cur->_right;}else if (kot(cur->_date) > key){cur = cur->_left;}else{//这里返回迭代器需要传入root//是为了End(),这个我们后面再说//这里找到了返回当前位置的迭代器return Iterator(cur, _root);}}//找不到返回指向nullptr的迭代器return Iterator(nullptr,_root);
}//判断是否为红黑树bool ISRBTree(){if (_root == nullptr){return true;}int BlackCount = 0;Node* cur = _root;while (cur){//我们获得最左边路径黑色节点的个数if (cur->_col == BLACK)BlackCount++;cur = cur->_left;}int count = 0;return _ISRBTree(_root, count, BlackCount);}//判断是否为红黑树的子函数bool _ISRBTree(Node* root, int count, int BlackCount){if (root == nullptr){if (count != BlackCount)return false;elsereturn true;}Node* cur = root;if (cur->_col == BLACK)count++;if (cur->_col == RED && cur->_parent->_col == RED)return false;return _ISRBTree(root->_left, count, BlackCount) && _ISRBTree(root->_right, count, BlackCount);}void InOrder(){_InOrder(_root);cout << endl;}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first;cout << endl;_InOrder(root->_right);}Node* _root = nullptr;
};
8.2 正向迭代器的代码
在红黑中的代码中,我们是有包含迭代器的代码,但是这里为了更好的了解,我在单独拿出来展示
template<class T, class Ref, class Ptr>
class RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;
public:RBTreeIterator(Node* node, Node* root):_node(node), _root(root){}//判断两个正向迭代器是否不同bool operator!=(const Self& s) const{return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个}//判断两个正向迭代器是否相同bool operator==(const Self& s) const{return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个}Ref operator*(){return _node->_date;}Ptr operator->(){return &(_node->_date);}Self operator++(){Node* cur = _node;//如果右边不为空,那就找右边中序(最左边)if (cur->_right){Node* leftMost = cur->_right;while (leftMost->_left){leftMost = leftMost->_left;}_node = leftMost;}else{Node* parent = cur->_parent;//右为空有两种情况//下面这种情况就是一路找祖先parent->_right == curwhile (parent && parent->_right == cur){cur = parent;parent = parent->_parent;}cur = parent;_node = cur;}return *this;}Self operator--(){if (_node == nullptr){Node* cur = _root;while (cur->_right){cur = cur->_right;}_node = cur;}else if (_node->_left){Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const Self s1){return _node != s1._node;}private:Node* _node;Node* _root;
};
8.3 set的代码
template <class K>
class set
{struct SetKeyOfT{const K& operator()(const K& k){return k;}};
public:typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::Const_Iterator const_iterator;pair<iterator, bool> insert(const K& key){return _t.Insert(key);}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin()const{return _t.Begin();}const_iterator end()const{return _t.End();}iterator find(const K& key) {_t.Find(key);}private:RBTree<K, const K, SetKeyOfT> _t;
};
8.4 map的代码
template<class K, class V>
class map
{struct MapKeyOfT{const K& operator()(const pair<K, V>& k){return k.first;}};
public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Const_Iterator const_iterator;pair<iterator, bool> insert(const pair<K, V>& T){return _t.Insert(T);}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}V& operator[](const K& k){pair<iterator, bool> ret = _t.Insert({k, V()});return ret.first->second;}iterator find(const K& key){return _t.Find(key);}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
相关文章:
用一颗红黑树同时封装出map和set
目录 1. 红黑树源代码 2. 红黑树模版参数的控制 3. 红黑树节点当中存储的数据 4. 模板参数中仿函数的增加 5. 正向迭代器的实现 6. set模拟实现 7. map的模拟实现 8. 封装后的代码 8.1 红黑树的代码 8.2 正向迭代器的代码 8.3 set的代码 8.4 map的代码 1. 红黑树源…...
C Sharp上位机需要掌握哪些知识?
学历不高就不要有进大厂的想法了,你就在上位机这一条路上走到底。 .NET桌面程序开发有WPF和Winform。Winform比较简单,拖拖控件,难度不大,这种级别的开发,新人上手一个月就够了,但是不会有哪家公司专门招聘…...
【自学笔记】Spark基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 Apache Spark基础知识点总览目录简介核心组件Spark SQLDataFrame与Dataset APIRDD(弹性分布式数据集)Spark StreamingMLlib(机器…...
基于Spring Boot的智能停车计费系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
数据不外传!通过内网穿透实现绿联NAS远程访问的安全配置方案
文章目录 前言1. 开启ssh服务2. ssh连接3. 安装cpolar内网穿透4. 配置绿联NAS公网地址 前言 大家好,今天要带给大家一个超级酷炫的技能——如何让绿联NAS秒变‘千里眼’,通过简单的几步操作就能轻松实现内网穿透。想象一下,无论你身处何地&a…...
地理信息可视化技术大全【WebGIS 教程一】
前言: 在当今数据驱动的时代,地理信息技术(GIS)和空间数据可视化已成为科学研究、商业决策和智慧城市建设的重要工具。随着Web技术的快速发展,基于浏览器端的地图渲染和地理信息处理能力显著增强,各类开源与…...
huggingface datasets库中的load_dataset方法-------deepseek问答记录
1. 基本介绍 Hugging Face 的 datasets 库中的 load_dataset 方法是用于加载数据集的核心工具,它支持从多种来源(如本地文件、Hugging Face Hub、内存数据等)加载数据集,并返回标准的 Dataset 或 DatasetDict 对象,方…...
网络故障排查
网络故障排查 导航 一、电脑端排查 引起网络故障的原因有很多,我按照实际处理遇到的问题的频率还有检测所需时间尽可能短开始进行排查,建议按下面的顺序来排查 电脑网口 首先,应该检查该网口是否正常闪烁黄灯 如果没有亮灯,抓…...
字符串匹配问题(strs)(信息学奥赛一本通-1355)
【题目描述】 字符串中只含有括号 (),[],<>,{},判断输入的字符串中括号是否匹配。如果括号有互相包含的形式,从内到外必须是<>,(),[],{},例如。输入: [()] 输出:YES,而输入([]),([)]都应该输出NO。 【输入】 第一行为…...
MD2Card(markdown)
MD2Card 介绍: 1.小红书爆款神器,Markdown笔记秒转高颜值卡片 2.实时预览15种主题,自动拆长文,图片/SVG导出即用 3.零门槛不登录,免费无限生成,专治排版废和设计手残党 网站地址: https://md2…...
企业微信实现“关联外部选项“、“审批控件中的外部选项“
企业微信实现"关联外部选项"、"审批控件中的外部选项" 需求背景参考文档 需求背景 公司自定义了运营成本审批流程的模板,需要调用公司API获取小区列表(关联外部选项),将选中的值带入到审批里面来。开通配置权限请参考下面参考文档&…...
[实操]MySQL8 读写分离后,配合redis的方法与步骤
之前的文章已经提供相关MySQL8的主从与读写分离操作,为了在高并发场景中有更多的实际用处,于是编写该文章说明MySQL8在实现读写分离后结合Redis的方法与步骤。 以下是文中提到的中间件及其版本: 以下是更新后的表格,包含了中间件…...
深度学习技术与应用的未来展望:从基础理论到实际实现
深度学习作为人工智能领域的核心技术之一,近年来引起了极大的关注。它不仅在学术界带来了革命性的进展,也在工业界展现出了广泛的应用前景。从图像识别到自然语言处理,再到强化学习和生成对抗网络(GAN),深度…...
JavaScript中匿名函数与箭头函数之间的区别与联系
什么是匿名函数和箭头函数? 匿名函数:顾名思义,是没有名称的函数,通常在定义时立即使用或赋值给变量。它是JavaScript中传统的函数定义方式。 箭头函数:是ES6(ECMAScript 2015)引入的一种新语法…...
ARCGIS PRO SDK ProWindow自定义窗口DataGrid控件的应用
ProWindow 是ArcGIS Pro SDK中用于创建自定义窗口的关键类,帮助开发者扩展ArcGIS Pro的功能和用户界面。这些窗口可以嵌入到ArcGIS Pro的主界面中,提供与核心功能的无缝集成。 创建一个窗体xml: controls:ProWindowxmlns"http://schem…...
高效PDF翻译解决方案:多引擎支持+格式零丢失
软件介绍 在AI翻译工具大行其道的今天,传统翻译软件市场逐渐饱和,但专业领域的深度需求依然存在。本文推荐的PDF翻译工具凭借20余种专业翻译接口,为学术文献、技术文档等复杂内容提供更精准的翻译服务,在保留文档原始排版的同时…...
Spring Boot
一.SpringBoot配置文件 有三种种配置文件:application.yaml,application.yml,application.properties,但是我们一般使用yml结尾的配置文件其它一般不用。 1.properties 配置⽂件说明 ①基本语法和配置文件的读取 // 配置文件的…...
使用CSS3实现炫酷的3D翻转卡片效果
使用CSS3实现炫酷的3D翻转卡片效果 这里写目录标题 使用CSS3实现炫酷的3D翻转卡片效果项目介绍技术要点分析1. 3D空间设置2. 核心CSS属性3. 布局和定位 实现难点和解决方案1. 3D效果的流畅性2. 卡片内容布局3. 响应式设计 性能优化建议浏览器兼容性总结 项目介绍 在这个项目中…...
Excel 小黑第19套
对应大猫19 鼠标右键标签修改颜色 将文本文件导入工作表中:数据 -现有链接 -浏览更多 选择员工档案 (若预览是乱七八糟的文字,将文件格式改成简体中文)分隔符号看题目要求 注意:将身份证号设置为文本格式 将一列数…...
IDEA批量替换项目下所有文件中的特定内容
文章目录 1. 问题引入2. 批量替换项目下所有文件中的特定内容2.1 右键项目的根目录,点击在文件中替换2.2 输入要替换的内容 3. 解决替换一整行文本后出现空行的问题4. 增加筛选条件提高匹配的精确度 更多 IDEA 的使用技巧可以查看 IDEA 专栏: IDEA 1. 问…...
从零构建大语言模型全栈开发指南:第二部分:模型架构设计与实现-2.1.1自注意力机制(Scaled Dot-Product Attention)的逐行代码实现
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 2.1.1 自注意力机制(Scaled Dot-Product Attention)的逐行代码实现1. 自注意力机制的核心原理与数学表达1.1 注意力计算的三元组:`Q, K, V`2. 逐行代码实现与解析2.1 输入嵌入与权重矩阵初始化2.2 完…...
深入理解 Collections.emptyList():优雅处理空列表的利器!!!
🚀 深入理解 Collections.emptyList():优雅处理空列表的利器!🔧 大家好!👋 今天我们来聊聊 Java 中一个非常实用但容易被忽视的小工具——Collections.emptyList()。🎉 如果你经常需要返回一个…...
数据结构-ArrayList
文章目录 1. 线性表2. 顺序表3. ArrayList4. ArrayList的问题以及思考4.2 增容的性能消耗问题4.3 空间浪费问题 1. 线性表 线性表(Linear List)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见线性表&…...
[快乐学坊_2] 后端api测试
[快乐学坊management_1] With Cursor | Mysql设计 | 服务接口设计与开发 在 apifox 测试发现,500 报错,因为AI 辅助配置的数据库有点问题我们要来进行更改一下 重连一下我们上一篇文章中 配置好了的 mysql 然后就可以观察到,连接 成功了。我…...
盛铂科技国产SLMF315超低相位噪声频率综合器介绍
SLMF315频率综合器简介: 盛铂科技SLMF315超低相位噪声频率综合器的频率范围覆盖200MHz至15GHz。频率的最小步进仅为0.1Hz,在不考虑频率精度的情况下频率步进可达0.04Hz。SLMF315内部采用多环路设计从而获得极优秀的相位噪声特性,频率输出为1…...
用 pytorch 从零开始创建大语言模型(六):对分类进行微调
用 pytorch 从零开始创建大语言模型(六):对分类进行微调 6 微调用于分类6.1 微调的不同类别6.2 准备数据集6.3 创建数据加载器6.4 使用预训练权重初始化模型6.5 添加分类头部6.6 计算分类损失和准确率6.7 在监督数据上微调模型6.8 使用LLM进…...
Android Compose 层叠布局(ZStack、Surface)源码深度剖析(十三)
Android Compose 层叠布局(ZStack、Surface)源码深度剖析 一、引言 在 Android 应用开发领域,用户界面(UI)的设计与实现一直是至关重要的环节。随着技术的不断演进,Android Compose 作为一种全新的声明式…...
计算机网络-2 物理层
【考纲内容】 (一)通信基础 信道、信号、带宽、码元、波特、速率、信源与信宿等基本概念; 奈奎斯特定理与香农定理;编码与调制; 电路交换、报文交换与分组交换;数据报与虚电路① 视频讲解 (二…...
如何解决微服务调用链性能问题(优化 JVM 配置,降低 Full GC 频率)
1. 问题背景 在微服务架构中,服务之间的调用链较长,且频繁的远程调用可能导致性能瓶颈。同时,JVM 的 Full GC(Full Garbage Collection)频繁发生会导致应用暂停时间过长,影响用户体验。具体问题表现为&…...
深入理解 C# 反射 的使用
总目录 前言 反射是.NET框架中一个强大的特性,允许程序在运行时检查和操作类型信息。通过反射,开发者可以动态地创建对象、调用方法、访问属性等,为程序提供了极大的灵活性。本文将详细讲解C#反射的使用方法及其应用场景。 一、什么是反射&a…...
Java面试第十三山!《设计模式》
大家好,我是陈一。如果文章对你有帮助,请留下一个宝贵的三连哦~ 万分感谢! 一、设计模式入门指南 1. 什么是设计模式? 设计模式是可复用的解决方案模板,用于解决软件开发中常见的架构问题。如同建筑领域的…...
AI+视频赋能智慧农业:EasyCVR打造全域可视化农场监管平台
随着科技的飞速发展,传统农业正加速向智慧农业转型,农场管理也迎来了前所未有的变革机遇。在这一进程中,如何有效整合先进的信息技术,实现农场的精准化、智能化管理,成为了摆在农场主和农业管理者面前的关键课题。 基于…...
wsl2配置xv6全解(包括22.04Jammy)
文章目录 获取xv6源代码Ubuntu20.04 Version安装指令成功测试参考MIT2021年官方文档 24.04 Version安装指令成功测试参考MIT2024年官方文档 Ubuntu 22.04没有官方文档? 配置大体流程1. 卸载原本qemu(如果之前安装了)2. clone qemu官方源代码&…...
区块链技术的应用场景和优势
区块链技术是一种分布式数据库技术,它的应用场景和优势包括但不限于以下几点: 金融领域:区块链可以用于数字货币的交易和结算,实现去中心化的金融交易,提供更安全、透明和高效的支付方式;另外,也…...
基于深度学习的相位调制算法步骤
1.构建网络结构 2.制作数据集 3.训练网络 4.引入评价指标 5.迭代优化 总结 通过以上步骤,可以实现基于深度学习的相位调制算法: 使用 U-Net 构建神经网络。 生成数据集并训练网络。 使用训练好的网络预测相位分布。 通过相关系数 γ 评估调制效果&…...
Linux的I2C总线的原理和结构详解
Linux的I2C总线的原理和结构讲解 我前面基本已经吃透了Platform总线,关于Platform总线的原理和结构,详情见下面三篇博文: https://blog.csdn.net/wenhao_ir/article/details/145023181 https://blog.csdn.net/wenhao_ir/article/details/14…...
深入理解Linux中的SCP命令:使用与原理
在Linux系统中,文件传输是一个常见的操作。无论是将文件从本地传输到远程服务器,还是从远程服务器下载文件到本地,SCP(Secure Copy Protocol)都是一个非常实用的工具。本文将详细介绍SCP命令的使用方法,并深…...
【Android】VehiclePropertyAccess引起CarService崩溃
VehiclePropertyAccess引起CarService崩溃 VehiclePropertyAccess VehiclePropertyAccess属性,用于定义车辆属性的访问权限。权限包括 读:READ,只可以读取,不能写入。 VehiclePropertyAccess:READ写:WRITE…...
小米AX6000解锁ssh避坑笔记
经过网上教程不断尝试,终于解锁成功。 环境信息: Win10 笔记本 + AX210 WIFI6E网卡Vmware 16小米AX60000.可以先备份路由器的配置信息 1.首先降级小米AX6000到1.0.55 1.0.55下载路径 升级时注意: 清除当前所有用户配置升级完成后,选择不自动升级2.升级完成后,笔记本重新…...
论华为 Pura X 折叠屏性能检测
在科技浪潮中,折叠屏手机以其创新形态掀起市场热潮。华为 Pura X 作为华为最新折叠手机,承载前沿科技与精湛工艺,成为行业焦点。它融合先进折叠屏技术与优质材质,致力于打破传统手机使用边界,为用户开启全新体验。但产…...
关于极端场景下,数据库更新与 MQ 消息一致性保障方案的详细总结
目录 一、核心问题场景 二、RocketMQ 事务消息方案 1. 核心机制 2. 执行流程 3. 关键优势 4. 局限性 三、消息表方案 1. 核心机制 2. 执行流程 3. 关键优势 4. 局限性 四、方案对比与选择 五、实施建议 六、总结 一、核心问题场景 当数据库更新后,若 MQ 消息未…...
面试题精选《剑指Offer》:JVM类加载机制与Spring设计哲学深度剖析-大厂必考
一、JVM类加载核心机制 🔥 问题5:类从编译到执行的全链路过程 完整生命周期流程图 关键技术拆解 编译阶段 查看字节码指令:javap -v Robot.class 常量池结构解析(CONSTANT_Class_info等) 类加载阶段 // 手动加载…...
透析主流CSS预处理器的区别
Sass 和 Less 是两种主流的 CSS 预处理器(CSS Preprocessor),它们通过扩展原生 CSS 的语法,提供了变量、嵌套、混合(Mixins)、函数等高级功能,帮助开发者编写更高效、可维护的样式代码。以下是它…...
Redis 本地安装
首先安装: https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-from-source/ 进入root目录 tar -xzvf redis-stable.tar.gz cd redis-stable make然后 install sudo make install最后可以直接启动 redis-server但是此时启…...
Android Launcher3 首屏图标锁定技术方案解析
一、需求背景与技术挑战 在Android 13系统定制开发中,需实现Launcher首屏图标固定功能。该需求需在以下技术维度进行突破: 拖拽事件拦截机制:需精准识别拖拽目标区域 布局层级判定:准确识别第一屏的布局标识 跨屏操作限制&…...
MySQL 处理重复数据:保留一条与两条的实现方案
在数据库管理中,处理重复数据是一项常见的任务。本文将详细介绍如何在 MySQL 数据库里,针对 test 表中 fd 和 fe 字段存在的重复数据进行处理,分别实现保留一条和两条数据的操作。 表结构与需求概述 假设 test 表包含三个字段:id…...
Go红队开发—CLI框架(一)
CLI开发框架 命令行工具开发,主要是介绍开发用到的包,集成了一个框架,只要学会了基本每个人都能开发安全工具了。 该文章先学flags包,是比较经典的一个包,相比后面要学习的集成框架这个比较自由比较细化点࿰…...
deque
deque概念 双端数组,可以对头端进行插入删除操作 deque和vector差别(就像数据结构中的栈和队列) vector对于头部的插入删除效率低,而deque则相对高效 vector和deque都支持随机访问,但是vector的随机访问效率低,而deque则相对高效…...
【Oracle资源损坏类故障】:详细了解坏块
目录 1、物理坏块与逻辑坏块 1.1、物理坏块 1.2、逻辑坏块 2、两个坏块相关的参数 2.1、db_block_checksum 2.2、db_block_checking 3、检测坏块 3.1、告警日志 3.2、RMAN 3.3、ANALYZE 3.4、数据字典 3.5、DBVERIFY 4、修复坏块 4.1、RMAN修复 4.2、DBMS_REPA…...
数据分析处理库-Pandas
1.1 Pandas概述 核心概念: Pandas 是基于 NumPy 的数据分析库,核心数据结构:Series(一维)和 DataFrame(二维)。 应用场景:数据清洗、转换、统计分析、时间序列处理。 特点&#x…...