C++(进阶五)--STL--用一颗红黑树封装map和set

目录
1.红黑树源码(简略版)
2.模板参数的控制
3.红黑树的结点
4.迭代器的实现
正向迭代器
反向迭代器
5.set的模拟实现
6.map的模拟实现
7.封装完成后的代码
RBTree.h
mymap.h
myset.h
1.红黑树源码(简略版)
下面我们将对一棵KV模型的红黑树进行封装,同时模拟实现出C++STL库当中的map和set,所用到的红黑树源码如下:
//枚举定义结点的颜色
enum Colour
{RED,BLACK
};//红黑树结点的定义
template<class K, class V>
struct RBTreeNode
{//三叉链RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;//存储的键值对pair<K, V> _kv;//结点的颜色int _col; //红/黑//构造函数RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};//红黑树的实现
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public://构造函数RBTree():_root(nullptr){}//拷贝构造RBTree(const RBTree<K, V>& t){_root = _Copy(t._root, nullptr);}//赋值运算符重载(现代写法)RBTree<K, V>& operator=(RBTree<K, V> t){swap(_root, t._root);return *this;}//析构函数~RBTree(){_Destroy(_root);_root = nullptr;}//查找函数Node* Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_kv.first) //key值小于该结点的值{cur = cur->_left; //在该结点的左子树当中查找}else if (key > cur->_kv.first) //key值大于该结点的值{cur = cur->_right; //在该结点的右子树当中查找}else //找到了目标结点{return cur; //返回该结点}}return nullptr; //查找失败}//插入函数pair<Node*, bool> Insert(const pair<K, V>& kv){if (_root == nullptr) //若红黑树为空树,则插入结点直接作为根结点{_root = new Node(kv);_root->_col = BLACK; //根结点必须是黑色return make_pair(_root, true); //插入成功}//1、按二叉搜索树的插入方法,找到待插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first) //待插入结点的key值小于当前结点的key值{//往该结点的左子树走parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first) //待插入结点的key值大于当前结点的key值{//往该结点的右子树走parent = cur;cur = cur->_right;}else //待插入结点的key值等于当前结点的key值{return make_pair(cur, false); //插入失败}}//2、将待插入结点插入到树中cur = new Node(kv); //根据所给值构造一个结点Node* newnode = cur; //记录新插入的结点(便于后序返回)if (kv.first < parent->_kv.first) //新结点的key值小于parent的key值{//插入到parent的左边parent->_left = cur;cur->_parent = parent;}else //新结点的key值大于parent的key值{//插入到parent的右边parent->_right = cur;cur->_parent = parent;}//3、若插入结点的父结点是红色的,则需要对红黑树进行调整while (parent&&parent->_col == RED){Node* grandfather = parent->_parent; //parent是红色,则其父结点一定存在if (parent == grandfather->_left) //parent是grandfather的左孩子{Node* uncle = grandfather->_right; //uncle是grandfather的右孩子if (uncle&&uncle->_col == RED) //情况1:uncle存在且为红{//颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else //情况2+情况3:uncle不存在 + uncle存在且为黑{if (cur == parent->_left){RotateR(grandfather); //右单旋//颜色调整grandfather->_col = RED;parent->_col = BLACK;}else //cur == parent->_right{RotateLR(grandfather); //左右双旋//颜色调整grandfather->_col = RED;cur->_col = BLACK;}break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理}}else //parent是grandfather的右孩子{Node* uncle = grandfather->_left; //uncle是grandfather的左孩子if (uncle&&uncle->_col == RED) //情况1:uncle存在且为红{//颜色调整uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else //情况2+情况3:uncle不存在 + uncle存在且为黑{if (cur == parent->_left){RotateRL(grandfather); //右左双旋//颜色调整cur->_col = BLACK;grandfather->_col = RED;}else //cur == parent->_right{RotateL(grandfather); //左单旋//颜色调整grandfather->_col = RED;parent->_col = BLACK;}break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理}}}_root->_col = BLACK; //根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)return make_pair(newnode, true); //插入成功}//删除函数bool Erase(const K& key){//用于遍历二叉树Node* parent = nullptr;Node* cur = _root;//用于标记实际的待删除结点及其父结点Node* delParentPos = nullptr;Node* delPos = nullptr;while (cur){if (key < cur->_kv.first) //所给key值小于当前结点的key值{//往该结点的左子树走parent = cur;cur = cur->_left;}else if (key > cur->_kv.first) //所给key值大于当前结点的key值{//往该结点的右子树走parent = cur;cur = cur->_right;}else //找到了待删除结点{if (cur->_left == nullptr) //待删除结点的左子树为空{if (cur == _root) //待删除结点是根结点{_root = _root->_right; //让根结点的右子树作为新的根结点if (_root){_root->_parent = nullptr;_root->_col = BLACK; //根结点为黑色}delete cur; //删除原根结点return true;}else{delParentPos = parent; //标记实际删除结点的父结点delPos = cur; //标记实际删除的结点}break; //进行红黑树的调整以及结点的实际删除}else if (cur->_right == nullptr) //待删除结点的右子树为空{if (cur == _root) //待删除结点是根结点{_root = _root->_left; //让根结点的左子树作为新的根结点if (_root){_root->_parent = nullptr;_root->_col = BLACK; //根结点为黑色}delete cur; //删除原根结点return true;}else{delParentPos = parent; //标记实际删除结点的父结点delPos = cur; //标记实际删除的结点}break; //进行红黑树的调整以及结点的实际删除}else //待删除结点的左右子树均不为空{//替换法删除//寻找待删除结点右子树当中key值最小的结点作为实际删除结点Node* minParent = cur;Node* minRight = cur->_right;while (minRight->_left){minParent = minRight;minRight = minRight->_left;}cur->_kv.first = minRight->_kv.first; //将待删除结点的key改为minRight的keycur->_kv.second = minRight->_kv.second; //将待删除结点的value改为minRight的valuedelParentPos = minParent; //标记实际删除结点的父结点delPos = minRight; //标记实际删除的结点break; //进行红黑树的调整以及结点的实际删除}}}if (delPos == nullptr) //delPos没有被修改过,说明没有找到待删除结点{return false;}//记录待删除结点及其父结点(用于后续实际删除)Node* del = delPos;Node* delP = delParentPos;//调整红黑树if (delPos->_col == BLACK) //删除的是黑色结点{if (delPos->_left) //待删除结点有一个红色的左孩子(不可能是黑色){delPos->_left->_col = BLACK; //将这个红色的左孩子变黑即可}else if (delPos->_right) //待删除结点有一个红色的右孩子(不可能是黑色){delPos->_right->_col = BLACK; //将这个红色的右孩子变黑即可}else //待删除结点的左右均为空{while (delPos != _root) //可能一直调整到根结点{if (delPos == delParentPos->_left) //待删除结点是其父结点的左孩子{Node* brother = delParentPos->_right; //兄弟结点是其父结点的右孩子//情况一:brother为红色if (brother->_col == RED){delParentPos->_col = RED;brother->_col = BLACK;RotateL(delParentPos);//需要继续处理brother = delParentPos->_right; //更新brother(否则在本循环中执行其他情况的代码会出错)}//情况二:brother为黑色,且其左右孩子都是黑色结点或为空if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK))){brother->_col = RED;if (delParentPos->_col == RED){delParentPos->_col = BLACK;break;}//需要继续处理delPos = delParentPos;delParentPos = delPos->_parent;}else{//情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空if ((brother->_right == nullptr) || (brother->_right->_col == BLACK)){brother->_left->_col = BLACK;brother->_col = RED;RotateR(brother);//需要继续处理brother = delParentPos->_right; //更新brother(否则执行下面情况四的代码会出错)}//情况四:brother为黑色,且其右孩子是红色结点brother->_col = delParentPos->_col;delParentPos->_col = BLACK;brother->_right->_col = BLACK;RotateL(delParentPos);break; //情况四执行完毕后调整一定结束}}else //delPos == delParentPos->_right //待删除结点是其父结点的左孩子{Node* brother = delParentPos->_left; //兄弟结点是其父结点的左孩子//情况一:brother为红色if (brother->_col == RED) //brother为红色{delParentPos->_col = RED;brother->_col = BLACK;RotateR(delParentPos);//需要继续处理brother = delParentPos->_left; //更新brother(否则在本循环中执行其他情况的代码会出错)}//情况二:brother为黑色,且其左右孩子都是黑色结点或为空if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK))){brother->_col = RED;if (delParentPos->_col == RED){delParentPos->_col = BLACK;break;}//需要继续处理delPos = delParentPos;delParentPos = delPos->_parent;}else{//情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空if ((brother->_left == nullptr) || (brother->_left->_col == BLACK)){brother->_right->_col = BLACK;brother->_col = RED;RotateL(brother);//需要继续处理brother = delParentPos->_left; //更新brother(否则执行下面情况四的代码会出错)}//情况四:brother为黑色,且其左孩子是红色结点brother->_col = delParentPos->_col;delParentPos->_col = BLACK;brother->_left->_col = BLACK;RotateR(delParentPos);break; //情况四执行完毕后调整一定结束}}}}}//进行实际删除if (del->_left == nullptr) //实际删除结点的左子树为空{if (del == delP->_left) //实际删除结点是其父结点的左孩子{delP->_left = del->_right;if (del->_right)del->_right->_parent = delP;}else //实际删除结点是其父结点的右孩子{delP->_right = del->_right;if (del->_right)del->_right->_parent = delP;}}else //实际删除结点的右子树为空{if (del == delP->_left) //实际删除结点是其父结点的左孩子{delP->_left = del->_left;if (del->_left)del->_left->_parent = delP;}else //实际删除结点是其父结点的右孩子{delP->_right = del->_left;if (del->_left)del->_left->_parent = delP;}}delete del; //实际删除结点return true;}private://拷贝树Node* _Copy(Node* root, Node* parent){if (root == nullptr){return nullptr;}Node* copyNode = new Node(root->_data);copyNode->_parent = parent;copyNode->_left = _Copy(root->_left, copyNode);copyNode->_right = _Copy(root->_right, copyNode);return copyNode;}//析构函数子函数void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;//建立subRL与parent之间的联系parent->_right = subRL;if (subRL)subRL->_parent = parent;//建立parent与subR之间的联系subR->_left = parent;parent->_parent = subR;//建立subR与parentParent之间的联系if (parentParent == nullptr){_root = subR;_root->_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;Node* parentParent = parent->_parent;//建立subLR与parent之间的联系parent->_left = subLR;if (subLR)subLR->_parent = parent;//建立parent与subL之间的联系subL->_right = parent;parent->_parent = subL;//建立subL与parentParent之间的联系if (parentParent == nullptr){_root = subL;_root->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}//左右双旋void RotateLR(Node* parent){RotateL(parent->_left);RotateR(parent);}//右左双旋void RotateRL(Node* parent){RotateR(parent->_right);RotateL(parent);}Node* _root; //红黑树的根结点
};
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。
3.红黑树的结点
前面说到,由于上层容器的不同,底层红黑树当中的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>键值对。

结点更改后代码:
//红黑树结点的定义
template<class T>
struct RBTreeNode
{//三叉链RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;//存储的数据T _data;//结点的颜色int _col; //红/黑//构造函数RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};
4.模板参数仿函数的增加
我们插入或者查找操作时需要进行键值的比对,但结点里的T可能是key,也有可能是<Key,value>,那么这里进行比对时需要提供一个仿函数,当上层是set的时候,T就是键值Key,直接比对即可,当上层是map时,我们需要获取键值对当中的Key进行比较。
仿函数,就是使一个类的使用看上去像一个函数。其实现就是类中实现一个
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容器也需要向底层红黑树传入一个仿函数,虽然这个仿函数单独看起来没什么用,但却是必不可少的。
template<class K>
class set
{//仿函数struct SetKeyOfT{const K& operator()(const K& key) //返回键值Key{return key;}};
public://...
private:RBTree<K, K, SetKeyOfT> _t;
};
此时,set容器传入底层红黑树的就是set的仿函数,map容器传入底层红黑树的就是map的仿函数。

这样一来,当底层红黑树需要进行两个结点之间键值的比较时,都会通过传入的仿函数来获取相应结点的键值,然后再进行比较,下面以红黑树的查找函数为例:
//查找函数
iterator Find(const K& key)
{KeyOfT kot;Node* cur = _root;while (cur){if (key < kot(cur->_data)) //key值小于该结点的值{cur = cur->_left; //在该结点的左子树当中查找}else if (key > kot(cur->_data)) //key值大于该结点的值{cur = cur->_right; //在该结点的右子树当中查找}else //找到了目标结点{return iterator(cur); //返回该结点}}return end(); //查找失败
}
4.迭代器的实现
正向迭代器
红黑树的正向迭代器实际上就是对结点指针进行了封装,因此在正向迭代器当中实际上就只有一个成员变量,那就是正向迭代器所封装结点的指针。
//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{typedef RBTreeNode<T> Node; //结点的类型typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器的类型Node* _node; //正向迭代器所封装结点的指针// Node* _root;
};
因此,我们通过一个结点的指针便可以构造出一个正向迭代器。 有的在迭代器中会给一个根结点,我们这里没有给。
//构造函数
__TreeIterator(Node* node):_node(node) //根据所给结点指针构造一个正向迭代器//,_root(root)
{}
当对正向迭代器进行解引用操作时,我们直接返回对应结点数据的引用即可。
Ref operator*()
{return _node->_data; //返回结点数据的引用
}
当对正向迭代器进行->操作时,我们直接返回对应结点数据的指针即可。
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; //判断两个正向迭代器所封装的结点是否是同一个
}
红黑树正向迭代器实现时,真正的难点实际上
++和--运算符的重载。

实现红黑树的正向迭代器时,一个结点的正向迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点。
具体逻辑如下:
如果当前结点的右子树不为空,则++操作后应该找到其右子树当中的最左结点。
如果当前结点的右子树为空,则++操作后应该在该结点的祖先结点中,找到孩子不在父亲右的祖先。
代码如下:
//前置++
Self operator++()
{if (_node->_right) //结点的右子树不为空{//寻找该结点右子树当中的最左结点Node* left = _node->_right;while (left->_left){left = left->_left;}_node = left; //++后变为该结点}else //结点的右子树为空{//寻找孩子不在父亲右的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent&&cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent; //++后变为该结点}return *this;
}
实现红黑树的正向迭代器时,一个结点的正向迭代器进行--操作后,应该根据红黑树中序遍历的序列找到当前结点的前一个结点。
具体逻辑如下:
如果当前结点的左子树不为空,则--操作后应该找到其左子树当中的最右结点。
如果当前结点的左子树为空,则--操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。
代码如下:
//前置--
Self operator--()
{if (_node->_left) //结点的左子树不为空{//寻找该结点左子树当中的最右结点Node* right = _node->_left;while (right->_right){right = right->_right;}_node = right; //--后变为该结点}else //结点的左子树为空{//寻找孩子不在父亲左的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent; //--后变为该结点}return *this;
}
正向迭代器实现后,我们需要在红黑树的实现当中进行迭代器类型的typedef。需要注意的是,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在public区域进行typedef。
然后在红黑树当中实现成员函数begin和end:
begin函数返回中序序列当中第一个结点的正向迭代器,即最左结点。
end函数返回中序序列当中最后一个结点下一个位置的正向迭代器,这里直接用空指针构造一个正向迭代器。
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node; //结点的类型
public:typedef __TreeIterator<T, T&, T*> iterator; //正向迭代器iterator begin(){//寻找最左结点Node* left = _root;while (left&&left->_left){left = left->_left;}//返回最左结点的正向迭代器return iterator(left);}iterator end(){//返回由nullptr构造得到的正向迭代器(不严谨)return iterator(nullptr);}
private:Node* _root; //红黑树的根结点
};
在C++STL中,底层红黑树实现正向迭代器时所用的结构:
实际上,上述所实现的迭代器是有缺陷的,因为理论上我们对end()位置的正向迭代器进行--操作后,应该得到最后一个结点的正向迭代器,但我们实现end()时,是直接返回由nullptr构造得到的正向迭代器的,因此上述实现的代码无法完成此操作。
下面我们来看看C++SLT库当中的实现逻辑:

C++STL库当中实现红黑树时,在红黑树的根结点处增加了一个头结点,该头结点的左指针指向红黑树当中的最左结点,右指针指向红黑树当中的最右结点,父指针指向红黑树的根结点。
在该结构下,实现begin()时,直接用头结点的左孩子构造一个正向迭代器即可,实现rbegin()时,直接用头结点的右孩子构造一个反向迭代器即可(实际是先用该结点构造一个正向迭代器,再用正向迭代器构造出反向迭代器),而实现end()和rend()时,直接用头结点构造出正向和反向迭代器即可。此后,通过对逻辑的控制,就可以实现end()进行--操作后得到最后一个结点的正向迭代器。
但实现该结构需要更改当前很多函数的逻辑,例如插入结点时,若插入到了红黑树最左结点的左边,或最右结点的右边,此时需要更新头结点左右指针的指向,更新时又要进行遍历,也是需要成本,这里就不一一实现了,感兴趣的同学可以自己尝试一下。
反向迭代器
红黑树的反向迭代器实际上就是正向迭代器的一个封装,因此红黑树的反向迭代器就是一个迭代器适配器。
//反向迭代器---迭代器适配器
template<class Iterator>
struct ReverseIterator
{typedef ReverseIterator<Iterator> Self; //反向迭代器的类型typedef typename Iterator::reference Ref; //结点指针的引用typedef typename Iterator::pointer Ptr; //结点指针Iterator _it; //反向迭代器所封装的正向迭代器//构造函数ReverseIterator(Iterator it):_it(it) //根据所给正向迭代器构造一个反向迭代器{}Ref operator*(){return *_it; //通过调用正向迭代器的operator*返回结点数据的引用}Ptr operator->(){return _it.operator->(); //通过调用正向迭代器的operator->返回结点数据的指针}//前置++Self& operator++(){--_it; //调用正向迭代器的前置--return *this;}//前置--Self& operator--(){++_it; //调用正向迭代器的前置++return *this;}bool operator!=(const Self& s) const{return _it != s._it; //调用正向迭代器的operator!=}bool operator==(const Self& s) const{return _it == s._it; //调用正向迭代器的operator==}
};
需要注意的是,反向迭代器只接收了一个模板参数,即正向迭代器的类型,也就是说,反向迭代器不知道结点的引用类型和结点的指针类型,因此我们需要在正向迭代器当中对这两个类型进行typedef,这样反向迭代器才能通过正向迭代器获取结点的引用类型和结点的指针类型。
//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{typedef Ref reference; //结点指针的引用typedef Ptr pointer; //结点指针
};
还有一个const迭代器,比较简单,直接在正向迭代器或者反向迭代器上修改即可,后面会附加全部代码。
这里我们也需要在红黑树的实现当中进行迭代器类型的typedef,并在红黑树当中实现成员函数rbegin和rend:
rbegin函数返回中序序列当中最后一个结点的反向迭代器,即最右结点。
rend函数返回中序序列当中第一个结点前一个位置的反向迭代器,这里直接用空指针构造一个反向迭代器。
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node; //结点的类型
public:typedef ReverseIterator<iterator> reverse_iterator; //反向迭代器reverse_iterator rbegin(){//寻找最右结点Node* right = _root;while (right&&right->_right){right = right->_right;}//返回最右结点的反向迭代器return reverse_iterator(iterator(right));}reverse_iterator rend(){//返回由nullptr构造得到的反向迭代器(不严谨)return reverse_iterator(iterator(nullptr));}
private:Node* _root; //红黑树的根结点
};
5.set的模拟实现
完成上述操作后,set的模拟实现也就很简单了,其中的成员函数都是调用底层红黑树的对应接口就行了,只不过需要注意将插入函数和查找函数返回值当中的结点指针改为迭代器即可。
class set
{//仿函数struct SetKeyOfT{const K& operator()(const K& key) //返回键值Key{return key;}};
public:typedef typename RBTree<K,const K, SetKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K,const K, SetKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器typedef typename RBTree<K,const K, SetKeyOfT>::Constiterator const_iterator; //正向迭代器iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}//插入函数pair<iterator, bool> insert(const K& key){return _t.Insert(key);}//删除函数void erase(const K& key){_t.Erase(key);}//查找函数iterator find(const K& key){return _t.Find(key);}
private:RBTree<K,const K, SetKeyOfT> _t;
};
6.map的模拟实现
map的模拟实现也是相同的道理,其成员函数也是调用底层红黑树的对应接口,但对于map来说,除了将插入函数和查找函数返回值当中的结点指针改为迭代器之外,还需要实现[]运算符的重载函数。
template<class K, class V>
class map
{//仿函数struct MapKeyOfT{const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key{return kv.first;}};
public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Constiterator const_iterator; //正向迭代器typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}//插入函数pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}//[]运算符重载函数V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));iterator it = ret.first;return it->second;}//删除函数void erase(const K& key){_t.Erase(key);}//查找函数iterator find(const K& key){return _t.Find(key);}
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
7.封装完成后的代码
虽然封装过程已经阐述完毕了,但在代码更改过程中还是有许多细节的,下面给出完整封装后的代码,附加测试代码。
RBTree.h
#pragma once
#include <iostream>
using namespace std;//枚举定义结点的颜色
enum Colour
{RED,BLACK
};//红黑树结点的定义
template<class T>
struct RBTreeNode
{//三叉链RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;//存储的数据T _data;//结点的颜色int _col; //红/黑//构造函数RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{typedef Ref reference; //结点指针的引用typedef Ptr pointer; //结点指针typedef RBTreeNode<T> Node; //结点的类型typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器的类型Node* _node; //正向迭代器所封装结点的指针//构造函数__TreeIterator(Node* node):_node(node) //根据所给结点指针构造一个正向迭代器{}Ref operator*(){return _node->_data; //返回结点数据的引用}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; //判断两个正向迭代器所封装的结点是否是同一个}//前置++Self operator++(){if (_node->_right) //结点的右子树不为空{//寻找该结点右子树当中的最左结点Node* left = _node->_right;while (left->_left){left = left->_left;}_node = left; //++后变为该结点}else //结点的右子树为空{//寻找孩子不在父亲右的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent; //++后变为该结点}return *this;}//前置--Self operator--(){if (_node->_left) //结点的左子树不为空{//寻找该结点左子树当中的最右结点Node* right = _node->_left;while (right->_right){right = right->_right;}_node = right; //--后变为该结点}else //结点的左子树为空{//寻找孩子不在父亲左的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent; //--后变为该结点}return *this;}
};//反向迭代器---迭代器适配器
template<class Iterator>
struct ReverseIterator
{typedef ReverseIterator<Iterator> Self; //反向迭代器的类型typedef typename Iterator::reference Ref; //结点指针的引用typedef typename Iterator::pointer Ptr; //结点指针Iterator _it; //反向迭代器所封装的正向迭代器//构造函数ReverseIterator(Iterator it):_it(it) //根据所给正向迭代器构造一个反向迭代器{}Ref operator*(){return *_it; //通过调用正向迭代器的operator*返回结点数据的引用}Ptr operator->(){return _it.operator->(); //通过调用正向迭代器的operator->返回结点数据的指针}//前置++Self& operator++(){--_it; //调用正向迭代器的前置--return *this;}//前置--Self& operator--(){++_it; //调用正向迭代器的前置++return *this;}bool operator!=(const Self& s) const{return _it != s._it; //调用正向迭代器的operator!=}bool operator==(const Self& s) const{return _it == s._it; //调用正向迭代器的operator==}
};//红黑树的实现
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node; //结点的类型
public:typedef __TreeIterator<T, T&, T*> iterator; //正向迭代器typedef __TreeIterator<T, const T&,const T*> Constiterator; typedef ReverseIterator<iterator> reverse_iterator; //反向迭代器reverse_iterator rbegin(){//寻找最右结点Node* right = _root;while (right && right->_right){right = right->_right;}//返回最右结点的反向迭代器return reverse_iterator(iterator(right));}reverse_iterator rend(){//返回由nullptr构造得到的反向迭代器(不严谨)return reverse_iterator(iterator(nullptr));}iterator begin(){//寻找最左结点Node* left = _root;while (left && left->_left){left = left->_left;}//返回最左结点的正向迭代器return iterator(left);}iterator end(){//返回由nullptr构造得到的正向迭代器(不严谨)return iterator(nullptr);}Constiterator begin() const{//寻找最左结点Node* left = _root;while (left && left->_left){left = left->_left;}//返回最左结点的正向迭代器return Constiterator(left);}Constiterator end() const{//返回由nullptr构造得到的正向迭代器(不严谨)return Constiterator(nullptr);}//构造函数RBTree():_root(nullptr){}//拷贝构造RBTree(const RBTree<K, T, KeyOfT>& t){_root = _Copy(t._root, nullptr);}//赋值运算符重载(现代写法)RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t){swap(_root, t._root);return *this; //支持连续赋值}//析构函数~RBTree(){_Destroy(_root);_root = nullptr;}//查找函数iterator Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){if (key < kot(cur->_data)) //key值小于该结点的值{cur = cur->_left; //在该结点的左子树当中查找}else if (key > kot(cur->_data)) //key值大于该结点的值{cur = cur->_right; //在该结点的右子树当中查找}else //找到了目标结点{return iterator(cur); //返回该结点}}return end(); //查找失败}//插入函数pair<iterator, bool> Insert(const T& data){if (_root == nullptr) //若红黑树为空树,则插入结点直接作为根结点{_root = new Node(data);_root->_col = BLACK; //根结点必须是黑色return make_pair(iterator(_root), true); //插入成功}//1、按二叉搜索树的插入方法,找到待插入位置KeyOfT kot;Node* cur = _root;Node* parent = nullptr;while (cur){if (kot(data) < kot(cur->_data)) //待插入结点的key值小于当前结点的key值{//往该结点的左子树走parent = cur;cur = cur->_left;}else if (kot(data) > kot(cur->_data)) //待插入结点的key值大于当前结点的key值{//往该结点的右子树走parent = cur;cur = cur->_right;}else //待插入结点的key值等于当前结点的key值{return make_pair(iterator(cur), false); //插入失败}}//2、将待插入结点插入到树中cur = new Node(data); //根据所给值构造一个结点Node* newnode = cur; //记录新插入的结点(便于后序返回)if (kot(data) < kot(parent->_data)) //新结点的key值小于parent的key值{//插入到parent的左边parent->_left = cur;cur->_parent = parent;}else //新结点的key值大于parent的key值{//插入到parent的右边parent->_right = cur;cur->_parent = parent;}//3、若插入结点的父结点是红色的,则需要对红黑树进行调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent; //parent是红色,则其父结点一定存在if (parent == grandfather->_left) //parent是grandfather的左孩子{Node* uncle = grandfather->_right; //uncle是grandfather的右孩子if (uncle && uncle->_col == RED) //情况1:uncle存在且为红{//颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else //情况2+情况3:uncle不存在 + uncle存在且为黑{if (cur == parent->_left){RotateR(grandfather); //右单旋//颜色调整grandfather->_col = RED;parent->_col = BLACK;}else //cur == parent->_right{RotateLR(grandfather); //左右双旋//颜色调整grandfather->_col = RED;cur->_col = BLACK;}break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理}}else //parent是grandfather的右孩子{Node* uncle = grandfather->_left; //uncle是grandfather的左孩子if (uncle && uncle->_col == RED) //情况1:uncle存在且为红{//颜色调整uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else //情况2+情况3:uncle不存在 + uncle存在且为黑{if (cur == parent->_left){RotateRL(grandfather); //右左双旋//颜色调整cur->_col = BLACK;grandfather->_col = RED;}else //cur == parent->_right{RotateL(grandfather); //左单旋//颜色调整grandfather->_col = RED;parent->_col = BLACK;}break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理}}}_root->_col = BLACK; //根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)return make_pair(iterator(newnode), true); //插入成功}//删除函数bool Erase(const K& key){KeyOfT kot;//用于遍历二叉树Node* parent = nullptr;Node* cur = _root;//用于标记实际的待删除结点及其父结点Node* delParentPos = nullptr;Node* delPos = nullptr;while (cur){if (key < kot(cur->_data)) //所给key值小于当前结点的key值{//往该结点的左子树走parent = cur;cur = cur->_left;}else if (key > kot(cur->_data)) //所给key值大于当前结点的key值{//往该结点的右子树走parent = cur;cur = cur->_right;}else //找到了待删除结点{if (cur->_left == nullptr) //待删除结点的左子树为空{if (cur == _root) //待删除结点是根结点{_root = _root->_right; //让根结点的右子树作为新的根结点if (_root){_root->_parent = nullptr;_root->_col = BLACK; //根结点为黑色}delete cur; //删除原根结点return true;}else{delParentPos = parent; //标记实际删除结点的父结点delPos = cur; //标记实际删除的结点}break; //进行红黑树的调整以及结点的实际删除}else if (cur->_right == nullptr) //待删除结点的右子树为空{if (cur == _root) //待删除结点是根结点{_root = _root->_left; //让根结点的左子树作为新的根结点if (_root){_root->_parent = nullptr;_root->_col = BLACK; //根结点为黑色}delete cur; //删除原根结点return true;}else{delParentPos = parent; //标记实际删除结点的父结点delPos = cur; //标记实际删除的结点}break; //进行红黑树的调整以及结点的实际删除}else //待删除结点的左右子树均不为空{//替换法删除//寻找待删除结点右子树当中key值最小的结点作为实际删除结点Node* minParent = cur;Node* minRight = cur->_right;while (minRight->_left){minParent = minRight;minRight = minRight->_left;}cur->_data = minRight->_data; //将待删除结点的_data改为minRight的_datadelParentPos = minParent; //标记实际删除结点的父结点delPos = minRight; //标记实际删除的结点break; //进行红黑树的调整以及结点的实际删除}}}if (delPos == nullptr) //delPos没有被修改过,说明没有找到待删除结点{return false;}//记录待删除结点及其父结点(用于后续实际删除)Node* del = delPos;Node* delP = delParentPos;//调整红黑树if (delPos->_col == BLACK) //删除的是黑色结点{if (delPos->_left) //待删除结点有一个红色的左孩子(不可能是黑色){delPos->_left->_col = BLACK; //将这个红色的左孩子变黑即可}else if (delPos->_right) //待删除结点有一个红色的右孩子(不可能是黑色){delPos->_right->_col = BLACK; //将这个红色的右孩子变黑即可}else //待删除结点的左右均为空{while (delPos != _root) //可能一直调整到根结点{if (delPos == delParentPos->_left) //待删除结点是其父结点的左孩子{Node* brother = delParentPos->_right; //兄弟结点是其父结点的右孩子//情况一:brother为红色if (brother->_col == RED){delParentPos->_col = RED;brother->_col = BLACK;RotateL(delParentPos);//需要继续处理brother = delParentPos->_right; //更新brother(否则在本循环中执行其他情况的代码会出错)}//情况二:brother为黑色,且其左右孩子都是黑色结点或为空if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK))){brother->_col = RED;if (delParentPos->_col == RED){delParentPos->_col = BLACK;break;}//需要继续处理delPos = delParentPos;delParentPos = delPos->_parent;}else{//情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空if ((brother->_right == nullptr) || (brother->_right->_col == BLACK)){brother->_left->_col = BLACK;brother->_col = RED;RotateR(brother);//需要继续处理brother = delParentPos->_right; //更新brother(否则执行下面情况四的代码会出错)}//情况四:brother为黑色,且其右孩子是红色结点brother->_col = delParentPos->_col;delParentPos->_col = BLACK;brother->_right->_col = BLACK;RotateL(delParentPos);break; //情况四执行完毕后调整一定结束}}else //delPos == delParentPos->_right //待删除结点是其父结点的左孩子{Node* brother = delParentPos->_left; //兄弟结点是其父结点的左孩子//情况一:brother为红色if (brother->_col == RED) //brother为红色{delParentPos->_col = RED;brother->_col = BLACK;RotateR(delParentPos);//需要继续处理brother = delParentPos->_left; //更新brother(否则在本循环中执行其他情况的代码会出错)}//情况二:brother为黑色,且其左右孩子都是黑色结点或为空if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK))){brother->_col = RED;if (delParentPos->_col == RED){delParentPos->_col = BLACK;break;}//需要继续处理delPos = delParentPos;delParentPos = delPos->_parent;}else{//情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空if ((brother->_left == nullptr) || (brother->_left->_col == BLACK)){brother->_right->_col = BLACK;brother->_col = RED;RotateL(brother);//需要继续处理brother = delParentPos->_left; //更新brother(否则执行下面情况四的代码会出错)}//情况四:brother为黑色,且其左孩子是红色结点brother->_col = delParentPos->_col;delParentPos->_col = BLACK;brother->_left->_col = BLACK;RotateR(delParentPos);break; //情况四执行完毕后调整一定结束}}}}}//进行实际删除if (del->_left == nullptr) //实际删除结点的左子树为空{if (del == delP->_left) //实际删除结点是其父结点的左孩子{delP->_left = del->_right;if (del->_right)del->_right->_parent = delP;}else //实际删除结点是其父结点的右孩子{delP->_right = del->_right;if (del->_right)del->_right->_parent = delP;}}else //实际删除结点的右子树为空{if (del == delP->_left) //实际删除结点是其父结点的左孩子{delP->_left = del->_left;if (del->_left)del->_left->_parent = delP;}else //实际删除结点是其父结点的右孩子{delP->_right = del->_left;if (del->_left)del->_left->_parent = delP;}}delete del; //实际删除结点return true;}private://拷贝树Node* _Copy(Node* root, Node* parent){if (root == nullptr){return nullptr;}Node* copyNode = new Node(root->_data);copyNode->_parent = parent;copyNode->_left = _Copy(root->_left, copyNode);copyNode->_right = _Copy(root->_right, copyNode);return copyNode;}//析构函数子函数void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;//建立subRL与parent之间的联系parent->_right = subRL;if (subRL)subRL->_parent = parent;//建立parent与subR之间的联系subR->_left = parent;parent->_parent = subR;//建立subR与parentParent之间的联系if (parentParent == nullptr){_root = subR;_root->_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;Node* parentParent = parent->_parent;//建立subLR与parent之间的联系parent->_left = subLR;if (subLR)subLR->_parent = parent;//建立parent与subL之间的联系subL->_right = parent;parent->_parent = subL;//建立subL与parentParent之间的联系if (parentParent == nullptr){_root = subL;_root->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}//左右双旋void RotateLR(Node* parent){RotateL(parent->_left);RotateR(parent);}//右左双旋void RotateRL(Node* parent){RotateR(parent->_right);RotateL(parent);}Node* _root; //红黑树的根结点
};
mymap.h
#pragma once
#include "RBTree.h"
namespace mymap //防止命名冲突
{template<class K, class V>class map{//仿函数struct MapKeyOfT{const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key{return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Constiterator const_iterator; //正向迭代器typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}//插入函数pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}//[]运算符重载函数V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));iterator it = ret.first;return it->second;}//删除函数void erase(const K& key){_t.Erase(key);}//查找函数iterator find(const K& key){return _t.Find(key);}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};void test_map1(){map<string, int> m;m.insert({ "苹果",1 });m.insert({ "草莓",3 });m.insert({ "香蕉",2 });m.insert({ "苹果",4 });map<string, int>::iterator it = m.begin();while (it != m.end()){//it->first += 'x';it->second += 'y';//cout << it.operator->()->first << ":" << it->second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;// const迭代器初始化时需要给模板参数加constconst map<string, const int> m1;map<string, const int>::const_iterator it1 = m1.begin();while (it1 != m1.end()){//it1->second += 'y';cout << it1->first << ":" << it1->second << endl;++it1;}cout << endl;}void test_map2(){string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };// 使用map计算水果的个数map<string, int> countMap;for (auto& str : arr){// 水果存在则value值++,不存在则新增该水果并将value值++countMap[str]++;}// 按照key值排序for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
}
myset.h
#pragma once
#include "RBTree.h"namespace myset //防止命名冲突
{template<class K>class set{//仿函数struct SetKeyOfT{const K& operator()(const K& key) //返回键值Key{return key;}};public:typedef typename RBTree<K,const K, SetKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K,const K, SetKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器typedef typename RBTree<K,const K, SetKeyOfT>::Constiterator const_iterator; //正向迭代器iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}//插入函数pair<iterator, bool> insert(const K& key){return _t.Insert(key);}//删除函数void erase(const K& key){_t.Erase(key);}//查找函数iterator find(const K& key){return _t.Find(key);}private:RBTree<K,const K, SetKeyOfT> _t;};void PrintSet(const set<int>& s){for (auto e : s){cout << e << " ";}cout << endl;}void test_set(){set<int> s;s.insert(4);s.insert(2);s.insert(5);s.insert(15);s.insert(7);s.insert(1);set<int>::iterator it = s.begin();while (it != s.end()){//*it += 5;// 不能修改cout << *it << " ";++it;}cout << endl;PrintSet(s);// 浅拷贝问题set<int> copy(s);// 拷贝构造PrintSet(copy);copy = s;// 赋值操作符重载PrintSet(copy);}
}
本篇博客到此结束,欢迎评论区留言~
相关文章:
C++(进阶五)--STL--用一颗红黑树封装map和set
目录 1.红黑树源码(简略版) 2.模板参数的控制 3.红黑树的结点 4.迭代器的实现 正向迭代器 反向迭代器 5.set的模拟实现 6.map的模拟实现 7.封装完成后的代码 RBTree.h mymap.h myset.h 1.红黑树源码(简略版) 下面我们…...
DeepSeek服务器繁忙问题的原因分析与解决方案
一、引言 随着人工智能技术的飞速发展,DeepSeek 等语言模型在众多领域得到了广泛应用。然而,在春节这段时间的使用过程中,用户常常遭遇服务器繁忙的问题,这不仅影响了用户的使用体验,也在一定程度上限制了模型的推广和…...
《手札·开源篇》数字化转型助力永磁电机企业降本增效:快速设计软件如何让研发效率提升40%?
数字化转型助力永磁电机企业降本增效:快速设计软件如何让研发效率提升40%? 一、痛点:传统研发模式正在吃掉企业的利润 永磁电机行业面临两大挑战: 研发周期长:一款新电机从设计到量产需6-12个月,电磁计算…...
飞算JavaAI :AI + 时代下的行业趋势引领者与推动者
在科技飞速发展的当下,AI 时代正以前所未有的速度重塑着各个行业的格局,而软件开发领域更是这场变革的前沿阵地。在众多创新力量之中,飞算JavaAI 脱颖而出,宛如一颗璀璨的新星,凭借其独树一帜的特性与强大功能&#x…...
【重新认识C语言----结构体篇】
目录 -----------------------------------------begin------------------------------------- 引言 1. 结构体的基本概念 1.1 为什么需要结构体? 1.2 结构体的定义 2. 结构体变量的声明与初始化 2.1 声明结构体变量 2.2 初始化结构体变量 3. 结构体成员的访…...
解决错误:CondaHTTPError: HTTP 000 CONNECTION FAILED for url
解决错误:CondaHTTPError: HTTP 000 CONNECTION FAILED for url 查看channels:vim ~/.condarcshow_channel_urls: true channels:- http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/- http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/…...
使用令牌桶算法通过redis实现限流
令牌桶算法是一种常用的限流算法,它可以平滑地控制请求的处理速率。在 Java 中结合 Redis 实现令牌桶算法,可以利用 Redis 的原子操作来保证多节点环境下的限流效果。 一 实现思路 初始化令牌桶:在 Redis 中存储令牌桶的相关信息࿰…...
Docker的进程和Cgroup概念
Docker的进程和Cgroup概念 容器里的进程组织或关系0号进程:containerd-shim1号进程:容器内的第一个进程进程收到信号后的三种反应两个特权信号在容器内执行 kill 命令的行为 Cgroup 介绍CPU Cgroup 中与 CFS 相关的参数Kubernetes 中的资源管理memory cg…...
Day68:类的多态
在面向对象编程(OOP)中,多态(Polymorphism)是指不同类的对象对同一消息作出响应的能力。换句话说,多态允许不同类的对象使用相同的方法名,但实现不同的行为。多态是通过继承和方法重写来实现的,通常可以分为方法重写和接口重载。 在 Python 中,多态常常通过方法重写来…...
一种解决SoC总线功能验证完备性的技术
1. 前言 通过总线将各个IP通过总线连接起来的SoC芯片是未来的大趋势,也是缩短芯片开发周期,抢先进入市场的常用方法。如何确保各个IP是否正确连接到总线上,而且各IP的地址空间分配是否正确,是一件很棘手的事情。本文提出了一种新…...
【Linux系统】线程:线程库 / 线程栈 / 线程库源码阅读学习
一、线程库 1、线程库介绍:命名与设计 命名:线程库通常根据其实现目的和平台特性进行命名。例如,POSIX标准定义了Pthreads(POSIX Threads),这是一个广泛使用的线程库规范,适用于多种操作系统。此…...
深度剖析 Redis:缓存穿透、击穿与雪崩问题及实战解决方案
一、缓存基本使用逻辑 在应用程序中,为了提高数据访问效率,常常会使用缓存。一般的缓存使用逻辑是:根据 key 去 Redis 查询是否有数据,如果命中就直接返回缓存中的数据;如果缓存不存在,则查询数据库&#…...
如何使用el-table的多选框
对el-table再次封装,使得功能更加强大! 本人在使用el-table时,因为用到分页,导致上一页勾选的数据在再次返回时,没有选中,故在原有el-table组件的基础之上再次进行了封装。 1.首先让某些不需要勾选的列表进…...
【工具变量】上市公司企业渐进式创新程度及渐进式创新锁定数据(1991-2023年)
测算方式: 参考顶刊《经济研究》孙雅慧(2024)老师的做法,用当期创新和往期创新的内容重叠度作为衡量渐进式创新程度的合理指标。通过搜集海量专利摘要,测算当前专利申请和既有专利的内容相似度,反映企业在…...
LM Studio 部署本地大语言模型
一、下载安装 1.搜索:lm studio LM Studio - Discover, download, and run local LLMs 2.下载 3.安装 4.更改成中文 二、下载模型(软件内下载) 1.选择使用代理,否则无法下载 2.更改模型下载目录 默认下载位置 C:\Users\用户名\.lmstudio\models 3.搜…...
嵌入式工程师面试经验分享与案例解析
嵌入式工程师岗位受到众多求职者的关注。面试流程严格,技术要求全面,涵盖C/C编程、数据结构与算法、操作系统、嵌入式系统开发、硬件驱动等多个方向。本文将结合真实案例,深入剖析嵌入式工程师的面试流程、常见问题及应对策略,帮助…...
英特尔至强服务器CPU销量创14年新低,AMD取得进展
过去几年是英特尔56年历史上最艰难的时期之一。该公司在晶圆代工、消费级处理器和服务器芯片等各个领域都面临困境。随着英特尔重组其晶圆代工业务,新的分析显示其服务器业务的现状和未来前景不容乐观。 英特尔最近发布的10-K文件显示:“数据中心和人工…...
判断您的Mac当前使用的是Zsh还是Bash:echo $SHELL、echo $0
要判断您的Mac当前使用的是Zsh还是Bash,可以使用以下方法: 查看默认Shell: 打开“终端”应用程序,然后输入以下命令: echo $SHELL这将显示当前默认使用的Shell。例如,如果输出是/bin/zsh,则说明您使用的是Z…...
使用Springboot实现MQTT通信
目录 一、MQ协议 MQTT 特点 MQTT 工作原理 MQTT 主要应用场景 MQTT 配置与注意事项 二、MQTT服务器搭建 三、参考案例 MQTT(Message Queuing Telemetry Transport)是一种基于发布/订阅模型的轻量级消息传输协议,常用于物联网ÿ…...
植物大战僵尸融合版(电脑/安卓)
《植物大战僵尸融合版》是一款由B站UP主“蓝飘飘fly”制作的同人策略塔防游戏,基于经典《植物大战僵尸》玩法,加入了独特的植物融合系统。 出于方便,软件是便携版,解压后双击即可畅玩。 游戏主页依旧是植物大战僵尸经典界面。右下…...
02DevOps基础环境准备
准备两台Linux的操作系统,最简单的方式就是在本机上使用虚拟机搭建两个操作系统(实际生产环境是两台服务器,虚拟机的方式用于学习使用) 我搭建的两台服务器的ip分别是192.168.1.10、192.168.1.11 192.168.1.10服务器用于安装doc…...
苍穹外卖-day12(工作台、数据导出)
工作台Apache POI导出运营数据Excel报表 功能实现:工作台、数据导出 工作台效果图: 数据导出效果图: 在数据统计页面点击数据导出:生成Excel报表 1. 工作台 1.1 需求分析和设计 1.1.1 产品原型 工作台是系统运营的数据看板&…...
说一下 Tcp 粘包是怎么产生的?
TCP 粘包是什么? TCP 粘包(TCP Packet Merging) 是指多个小的数据包在 TCP 传输过程中被合并在一起,接收方读取时无法正确分辨数据边界,导致数据解析错误。 TCP 是流式协议,没有数据包的概念,…...
详解享元模式
引言 在计算机中,内存是非常宝贵的资源,而程序中可能会有大量相似或相同的对象,它们的存在浪费了许多空间。而享元模式通过共享这些对象,从而解决这种问题的。 1.概念 享元模式(Flyweight Pattern):运用共享技术有效地…...
第18章 不可变对象设计模式(Java高并发编程详解:多线程与系统设计)
1.线程安全 所谓共享的资源,是指在多个线程同时对其进行访问的情况下,各线程都会使其发生变化,而线程安全性的主要目的就在于在受控的并发访问中防止数据发生变化。除了使用synchronized关键字同步对资源的写操作之外, 还可以在线…...
openEuler22.03LTS系统升级docker至26.1.4以支持启用ip6tables功能
本文记录了openEuler22.03LTS将docker升级由18.09.0升级至26.1.4的过程(当前docker最新版本为27.5.1,生产环境为保障稳定性,选择升级到上一个大版本26的最新小版本)。 一、现有环境 1、系统版本 [rootlocalhost opt]# cat /etc…...
< OS 有关 > Ubuntu 版本升级 实践 24.04 -> 24.10, 安装 .NET
原因: 想安装 .NET 9 去编译 GitHut 项目,这回用不熟悉的 Ubuntu来做,不知道怎么拐去给 Ubuntu 升级,看到现在版本是 24.10 但不是 LTS 版本,记录下升级过程。 一、实践过程: 1. 查看当前版本 命令1: l…...
某咨询大数据解决方案介绍(32页PPT)
本文档介绍了一个大数据平台解决方案,旨在解决企业当前面临的数据问题,包括数据定义缺失、重复采集和存储、数据不完整以及缺乏可靠决策依据等。通过引入大数据技术,该方案强调从被动的IT支撑向主动的数据核心服务转型,以实现科学…...
ZooKeeper作为注册中心有什么问题? ZooKeeper作为注册中心,海量服务同时重启有什么问题?
目录 ZooKeeper作为注册中心存在的问题 性能瓶颈 一致性保证 复杂性 扩展性 单点故障 数据模型限制 社区和生态 安全性 总结 ZooKeeper作为注册中心,海量服务同时重启有的问题 1. ZooKeeper集群压力剧增 2. ZooKeeper Leader节点压力 3. 会话和临时节点管理 4.…...
matlab simulink 汽车四分之一模型主动被动悬架-LQR
1、内容简介 略 matlab simulink 可以交流、咨询、答疑 124- 2、内容说明 略汽车悬架系统由弹性元件、导向元件和减振器组成,是车身与车轴之间连接的所有组合体零件的总称,也是车架(或承载式车身)与车桥(或车轮)之间一切力传递装置的总称,其主要功能是使车轮与地面有很好的…...
