C++模拟实现map和set
C++模拟实现map和set
- 1、STL源代码分析
- 2、实现出复用红黑树的框架
- 3、实现红黑树的迭代器
- 4、解决map和set中key不可修改问题
- 5、解决insert返回值问题
- 完整代码
模拟实现map和set实际上是对红黑树的封装,如对红黑树的实现有疑问,请移步:C++手撕红黑树
1、STL源代码分析
map和set的底层都是红黑树,但是对于set来说,只存储key,而对于map来说,需要存储key和value——也就是pair类型。那么我们是实现两份红黑树的代码吗?实则不然,因为他们只有存储类型上的区别,所以我们要尽量的实现代码复用,通过对一份红黑树的代码封装同时实现map和set。
我们先来看看库里是怎么实现的:
我们可以看到,树的节点是继承了一个__rb_tree_node_base的类,这个基类存储了颜色、左孩子、右孩子、父亲的指针,继承后多了一个value_field的变量,这个变量实际上就是存储的数据类型。
继续往下看:
map的前两个模板参数:Key和T对应的就是key和value。将Key重命名为key_type,将pair<Key, T>重命名为value_type。然后将key_type和value_type作为模板参数传给红黑树。
set有一个Key模板参数,将Key重命名为key_type,还将Key重命名为value_type。然后将这两个作为模板参数传给红黑树。
红黑树的前两个模板参数为:Key和Value。红黑树内部将Value作为模板参数传给了红黑树的节点,红黑树的节点中通过Value声明了value_field成员变量,value_field就是存储的数据类型,所以红黑树是通过模板参数Value来控制存储的类型的。
我们分析发现,当使用的是map时,会将pair<K, V>传给红黑树的模板参数Value,红黑树存储的数据类型就是pair<K, V>。当使用的是set时,就会将Key传给红黑树的模板参数Value,这时候Key就是存储的数据类型。
接下来,我们开始封装实现map和set,请你思考一下:既然红黑树这里是通过模板参数Value来控制数据类型的,那么是不是就不需要传Key了?
2、实现出复用红黑树的框架
首先我们来修改红黑树的代码:我们将存储的数据类型统一定义为T。先修改节点的模板参数,然后修改红黑树的模板。注意:由于修改了模板参数名,因此类内涉及模板参数名的代码都需要修改。
template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};template<class K, class T>
class RBTree
{typedef RBTreeNode<T> Node;
private:Node* _root = nullptr;
};
然后创建新文件:MySet.h和MyMap.h
#pragma once
#include "RBTree.h"namespace zzy
{template<class K>class set{public:private:RBTree<K, K> _t;};
}
#pragma once
#include "RBTree.h"namespace zzy
{template<class K, class V>class map{public:private:RBTree<K, pair<K, V>> _t;};
}
由于节点存储的数据类型我们改为_data,因此在红黑树中访问数据的代码都得修改。我们先来看Insert的修改:
bool Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_data < data){parent = cur;cur = cur->_right;}else if (cur->_data > data){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(data);if (parent->_data < data)parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}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);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}}_root->_col = BLACK;return true;
}
在插入数据的过程中,需要将插入的数据data和当前节点的数据_data进行比较,如果是set就可以直接比,如果是map需要按key比。但是这里的比较有问题:当T为pair时,cur->_data和data就是两个pair的比较了,那么我们看看库里pair是否重载了比较,如果重载了是否满足我们的需求?
库里果然实现了pair类的六个运算符重载比较函数,那么是否满足我们的要求?——按key,也就是first来比较。
阅读代码:库里实现的operator<是先比较first是否小于,如果first不小于再比较second是否小于。也就是说如果first小于则返回true,如果frist不小于但是second小于也返回true,这种比较方式是不符合我们的需求的。因此我们要自己实现比较。
那么如何实现呢?——仿函数
我们发现红黑树的模板参数还存在KeyOfValue,这个名字很形象,就是取出Value中的Key。对于set,传入key直接将key返回。对于map,传入pair<key, value>,将key返回。
仿函数——可以像函数一样实现,实际上就是重载了operator()。所以我们需要在map和set中自己定义并将类传给红黑树,然后在红黑树中实例化对象,然后通过operator()取出T中的key,然后进行比较。
实现如下:这里我们直接定义为内部类更为方便
#pragma once
#include "RBTree.h"namespace zzy
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:private:RBTree<K, K, SetKeyOfT> _t;};
}
#pragma once
#include "RBTree.h"namespace zzy
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:private:RBTree<K, pair<K, V>, MapKeyOfT> _t;};
}
然后给红黑树添加一个模板参数,并修改Insert函数如下:
bool Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(data);if (kot(parent->_data) < kot(data))parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}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);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}}_root->_col = BLACK;return true;
}
在Insert函数中,我们通过KeyOfT创建出kot对象,然后通过kot()并传入数据对象,该函数会将key返回。这样对于set,传入key返回key,没有变化。对于map传入pair<K, V>,返回pair的first。这样就可以适配map和set的比较了。这里可以说是set迁就于map,因为set本身就是key,但是map需要获取key。
其他函数如Erase、Find修改也是类似的,需要通过KeyOfT创建出kot对象,通过kot()仿函数来获取key进行比较。其他函数请你自行修改。
修改完毕后,可以将map和set的insert函数写出来,然后测试一下。
针对前面的问题:红黑树这里还是需要传入Key的,因为我们在查找、删除函数中需要用到。其他函数代码请你自行修改。
3、实现红黑树的迭代器
这里类似前面的list,由于空间不连续,所以我们需要创建自定义类型。
先来看看库里的实现:
基本上类似前面list的迭代器实现,只不过红黑树这里迭代器进行++和--会稍微复杂一点。
红黑树的迭代器遍历本质上是中序遍历,因此begin我们返回的是最左节点(最小节点),end我们返回最右节点的下一个数据就是nullptr。
我们先把其他函数写出来,这里的Ref和Ptr就是为了控制实现普通迭代器和const迭代器的。
template<class T, class Ref, class Ptr>
struct __TreeIterator
{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 &(operator*());}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};
下面我们要实现operator++和operator--:
Self& operator++()
{if (_node->_right){Node* subLeft = _node->_right;while (subLeft->_left)subLeft = subLeft->_left;_node = subLeft;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right == cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}Self operator++(int)
{Self tmp(_node);++* this;return tmp;
}Self& operator--()
{if (_node->_left){Node* subRight = _node->_left;while (subRight->_right)subRight = subRight->_right;_node = subRight;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}Self operator--(int)
{Self tmp(_node);--* this;return tmp;
}
紧接着在红黑树中加入如下代码:
typedef __TreeIterator<T, T&, T*> iterator;
typedef __TreeIterator<T, const T&, const T*> const_iterator;iterator begin()
{Node* leftMin = _root;while (leftMin && leftMin->_left)leftMin = leftMin->_left;return iterator(leftMin);
}iterator end()
{return iterator(nullptr);
}const_iterator begin() const
{Node* leftMin = _root;while (leftMin && leftMin->_left)leftMin = leftMin->_left;return iterator(leftMin);
}const_iterator end() const
{return iterator(nullptr);
}
然后在map和set中加入迭代器,就可以配合insert来进行测试了:
typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;iterator begin()
{return _t.begin();
}iterator end()
{return _t.end();
}
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;iterator begin()
{return _t.begin();
}iterator end()
{return _t.end();
}
上图是库里的实现方式,库里多了一个哨兵位的头节点,左孩子指向树中最左节点(最小节点),右孩子指向树中最右节点(最大节点)。通过这种实现方式可以快速返回begin(),但是也带来了维护较为麻烦的问题。这里了解一下即可。
4、解决map和set中key不可修改问题
测试insert和迭代器通过后,我们还需要解决map和set中key不可修改的问题。而现在我们的迭代器是可以修改的。那么如何实现呢?
对于set来说,不管是普通迭代器还是const迭代器都是不可修改的,所以直接将const迭代器作为普通迭代器来使用,这是库里的解决方式。
但是这么写会报错,因此_t还是普通对象,走的还是普通迭代器的begin,调用树的begin返回的也是普通迭代器,所以就出现了普通迭代器无法向const迭代器转换的问题。
处理方式:
1、支持普通迭代器构造const迭代器。
2、类似库里的实现方式。直接在后面加上const。不实现普通类型的迭代器函数了。
如上图,这样就解决了。
set的问题解决了,下面看map,map能这样实现吗?
答案是不行的,如果这样的话整个pair都不能修改了,不仅key不能修改,value也不能修改。但是我们只要key不可修改,value是可以修改的。
下面看库里的实现方式:
直接用const将key锁死,这样value还是可以修改的。
5、解决insert返回值问题
之前介绍map和set的使用,insert的返回值是一个pair<iterator, bool>的对象,现在我们需要对红黑树的返回值进行修改,同时实现map的operator[]重载函数。
我们将红黑树的insert函数返回值修改为pair<iterator, bool>,插入成功返回piar<新插入节点的迭代器,true>,插入失败返回pair<已存在节点的迭代器,false>。然后对map和set中insert的返回值进行修改。
修改后运行程序发现:
这是因为在set这里,无论是iterator还是const_iterator,它们实际上都是const_iterator。而我们的树是普通对象,调用insert之后返回的是普通的迭代器,所以就出现了pair<iterator,bool>向pair<const_iterator,bool>进行转换。但是这里是无法转换的。
我们可以这样处理:
pair<iterator, bool> insert(const K& key)
{pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);
}
先接受红黑树底层的返回值,然后通过ret.first和ret.second去构造新的pair返回。
但是现在还是无法运行:
这是因为ret.first是普通迭代器,要构造的pair的first是const迭代器,不支持普通迭代器向const迭代器的转换。所以我们需要实现一个普通迭代器构造const迭代器的函数,如下:
无论类模板实例化出const迭代器还是普通迭代器,这里的Iterator都是普通迭代器。
1、这个类被实例化为const迭代器时,这个函数是一个构造函数,支持普通迭代器向const迭代器转换。
2、这个类被实例化为普通迭代器时,这个函数是一个拷贝构造函数。
修改了Insert函数的返回值就可以来实现map中operator[]的重载了:
pair<iterator, bool> insert(const pair<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;
}
完整代码
// MySet.h
#pragma once
#include "RBTree.h"namespace zzy
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin() const{return _t.begin();}iterator end() const{return _t.end();}pair<iterator, bool> insert(const K& key){pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);}private:RBTree<K, K, SetKeyOfT> _t;};
}
// MyMap.h
#pragma once
#include "RBTree.h"namespace zzy
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.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;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}pair<iterator, bool> insert(const pair<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:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};
}
// RBTree.h
#pragma onceenum Colour {RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _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 RBTreeNode<T> Node;typedef __TreeIterator<T, Ref, Ptr> Self;typedef __TreeIterator<T, T&, T*> Iterator;Node* _node;__TreeIterator(Node* node):_node(node){}__TreeIterator(const Iterator& it):_node(it._node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &(operator*());}Self& operator++(){if (_node->_right){Node* subLeft = _node->_right;while (subLeft->_left)subLeft = subLeft->_left;_node = subLeft;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right == cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self operator++(int){Self tmp(_node);++* this;return tmp;}Self& operator--(){if (_node->_left){Node* subRight = _node->_left;while (subRight->_right)subRight = subRight->_right;_node = subRight;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self operator--(int){Self tmp(_node);--* this;return tmp;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};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*> const_iterator;iterator begin(){Node* leftMin = _root;while (leftMin && leftMin->_left)leftMin = leftMin->_left;return iterator(leftMin);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* leftMin = _root;while (leftMin && leftMin->_left)leftMin = leftMin->_left;return const_iterator(leftMin);}const_iterator end() const{return const_iterator(nullptr);}RBTree():_root(nullptr){}RBTree(const RBTree<K, T, KeyOfT>& t):_root(nullptr){_root = Copy(t._root, nullptr);}RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t){swap(_root, t._root);return *this;}~RBTree(){Destroy(_root);}pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < kot(data)){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);if (kot(parent->_data) < kot(data))parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;Node* newnode = cur;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateL(parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}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);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);grandfather->_col = RED;cur->_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;while (cur){if (kot(cur->_data) < key){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > key){parent = cur;cur = cur->_left;}else{if (cur->_left == nullptr){if (_root == cur){_root = cur->_right;if (_root){_root->_parent = nullptr;_root->_col = BLACK;}delete cur;return true;}}else if (cur->_right == nullptr){if (_root == cur){_root = cur->_left;if (_root){_root->_parent = nullptr;_root->_col = BLACK;}delete cur;return true;}}else{parent = cur;Node* rightMin = cur->_right;while (rightMin->_left){parent = rightMin;rightMin = rightMin->_left;}cur->_data = rightMin->_data;cur = rightMin;}break;}}if (cur == nullptr) return false;Node* del = cur;Node* delParent = parent;if (cur->_col == BLACK && !cur->_left && !cur->_right){while (parent){if (parent->_left == cur){Node* brother = parent->_right;if (brother->_col == RED){brother->_col = BLACK;parent->_col = RED;RotateL(parent);brother = parent->_right;}if ((!brother->_left || brother->_left->_col == BLACK)&& (!brother->_right || brother->_right->_col == BLACK)){brother->_col = RED;if (parent->_col == RED){parent->_col = BLACK;break;}cur = parent;parent = cur->_parent;}else{if (!brother->_right || brother->_right->_col == BLACK){brother->_left->_col = BLACK;brother->_col = RED;RotateR(brother);brother = parent->_right;}brother->_right->_col = BLACK;brother->_col = parent->_col;parent->_col = BLACK;RotateL(parent);break;}}else{Node* brother = parent->_left;if (brother->_col == RED){brother->_col = BLACK;parent->_col = RED;RotateR(parent);brother = parent->_left;}if ((!brother->_left || brother->_left->_col == BLACK)&& (!brother->_right || brother->_right->_col == BLACK)){brother->_col = RED;if (parent->_col == RED){parent->_col = BLACK;break;}cur = parent;parent = cur->_parent;}else{if (!brother->_left || brother->_left->_col == BLACK){brother->_right->_col = BLACK;brother->_col = RED;RotateL(brother);brother = parent->_left;}brother->_left->_col = BLACK;brother->_col = parent->_col;parent->_col = BLACK;RotateR(parent);break;}}}}cur = del, parent = delParent;if (cur->_left == nullptr){if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;if (cur->_right){cur->_right->_parent = parent;cur->_right->_col = BLACK;}}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;if (cur->_left){cur->_left->_parent = parent;cur->_left->_col = BLACK;}}delete cur;return true;}Node* Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key)cur = cur->_right;else if (kot(cur->_data) > key)cur = cur->_left;elsereturn cur;}return nullptr;}bool IsBalance(){return IsBalance(_root);}int Height(){return Height(_root);}void InOrder(){InOrder(_root);cout << endl;}private:void InOrder(Node* root){if (root == nullptr) return;InOrder(root->_left);cout << root->_kv.first << " ";InOrder(root->_right);}int Height(Node* root){if (root == nullptr) return 0;int left = Height(root->_left);int right = Height(root->_right);return left > right ? left + 1 : right + 1;}Node* Copy(Node* root, Node* parent){if (root == nullptr) return nullptr;Node* copy = new Node(root->_kv);copy->_col = root->_col;copy->_parent = parent;copy->_left = Copy(root->_left, copy);copy->_right = Copy(root->_right, copy);return copy;}void Destroy(Node*& root){if (root == nullptr) return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}bool IsBalance(Node* root){if (root == nullptr) return true;if (root->_col != BLACK) return false;int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK) benchmark++;cur = cur->_left;}return CheckColour(root, 0, benchmark);}bool CheckColour(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark) return false;return true;}if (root->_col == BLACK) blacknum++;if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << "出现连续的红色节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark)&& CheckColour(root->_right, blacknum, benchmark);}void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;Node* ppnode = parent->_parent;parent->_right = curleft;if (curleft)curleft->_parent = parent;cur->_left = parent;parent->_parent = cur;if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent)ppnode->_left = cur;elseppnode->_right = cur;cur->_parent = ppnode;}}void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;Node* ppnode = parent->_parent;parent->_left = curright;if (curright)curright->_parent = parent;cur->_right = parent;parent->_parent = cur;if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent)ppnode->_left = cur;elseppnode->_right = cur;cur->_parent = ppnode;}}
private:Node* _root = nullptr;
};
相关文章:
C++模拟实现map和set
C模拟实现map和set 1、STL源代码分析2、实现出复用红黑树的框架3、实现红黑树的迭代器4、解决map和set中key不可修改问题5、解决insert返回值问题完整代码 模拟实现map和set实际上是对红黑树的封装,如对红黑树的实现有疑问,请移步:C手撕红黑树…...
使用elasticdump导出/导入 -- ES数据
导出指定索引数据到指定文件夹: ./elasticdump --inputhttp://用户:密码IP:9201/索引名字 --output导出路径/out.json --typedata 将导出的文件导入 ./elasticdump --input路径/out.json --outputhttp://账号:密码IP:9201/索引名称 --typedata --fileTypejson 【el…...
CSDN年度评选揭晓,永洪科技AI技术与智能应用双星闪耀
近日,永洪科技在CSDN(中国专业开发者社区)的年度评选中,凭借在人工智能技术创新与vividime在行业应用中的卓越表现,一举斩获“人工智能企业”及“智能应用”双料大奖。这一荣誉不仅彰显了永洪科技在AI领域的领先地位&a…...
Kubernetes 资源利用率翻倍?离在线混合部署深度解析
还在为 Kubernetes 集群资源利用率低而烦恼?还在为高昂的云成本而头疼?今天,我们就来聊聊 Kubernetes 中的一项黑科技——离在线混合部署,让大家的集群资源利用率翻倍,成本减半! 🤔 什么是离在线…...
【Java】Spring Boot全量YAML配置说明
目录 Spring Boot 配置文件基础核心配置日志配置Web 服务器配置数据源配置JPA 配置缓存配置国际化配置邮件服务配置自定义配置使用示例1. Spring Boot 配置文件基础 Spring Boot 的配置文件可以使用以下文件格式: application.propertiesapplication.ymlSpring Boot 默认加载…...
【STL】7.STL常用算法(1)
STL常用算法(1) 前言简介一.遍历算法1.for_each2.transform 二.查找算法1.find2.find_if3.adjacent_find4.binary_search5.count6.cout_if 三.排序算法1.sort2.random_shuffle3.merge4.reverse 总结 前言 stl系列主要讲述有关stl的文章,使用S…...
弱监督语义分割学习计划(1)-简单实现CAM但是效果不好
零: 项目说明 是这样的一个事情,经过与deepseek的一番讨论和交流,DeepSeek为我设计了一个30天高强度学习计划,重点聚焦弱监督/无监督语义分割在野外场景的应用,结合理论与实践,并最终导向可落地的开源项目。目前开始了…...
内存泄漏问题分享
在前端开发中,内存泄漏(Memory Leak)是指由于代码问题导致浏览器无法回收不再使用的内存,从而影响网页的性能,导致页面变慢,甚至崩溃。前端内存泄漏通常由以下几种原因引起,理解和修复这些问题对…...
用 DeepSeek 打样!KubeSphere LuBan 用 3 天/3 分钟“干掉”大模型部署焦虑
用 DeepSeek 打样!KubeSphere LuBan 用 3 天/3 分钟“干掉”大模型部署焦虑 大模型落地,如何告别“部署焦虑”? DeepSeek-R1 的惊艳表现无需赘述,但企业落地时的高门槛却让许多开发者望而却步——复杂的部署流程、资源调度难题、…...
Java在云计算平台中的应用研究
Java在云计算平台中的应用研究 随着云计算的广泛应用,越来越多的企业和开发者开始选择基于云计算的架构来构建和部署应用。Java作为一种成熟的编程语言,凭借其跨平台性、强大的生态系统以及优秀的并发处理能力,已成为云计算平台中常用的编程…...
【学写LibreCAD】0 仿写LibreCAD简介
一、LibreCAD 核心模块: 核心模块(Core) 功能:处理 CAD 的核心逻辑,如几何计算、图形对象管理、坐标系转换等。关键组件: 图形对象:如直线、圆、圆弧、多段线等。数学工具:向量、矩…...
【质量管理】怎么评估职能部门当前质量管理成熟度
评估目的 在做质量管理时,我们需要先了解各职能部门当前质量管理成熟度。从而识别改进机会,为各职能部门后续质量提升计划提供依据。 直白说:就是让那些不肯动的人动起来,同时往往总经理对各部门的质量管理成熟度的评分要更低&…...
音乐游戏Dance Dance Revolution(DDR)模拟器
文章目录 (一)Dance Dance Revolution(1.1)基本情况(1.2)机体 (二)模拟器(2.1)主程序(2.2)模拟器主题 (三)曲谱…...
【Pandas】pandas Series filter
Pandas2.2 Series Computations descriptive stats 方法描述Series.align(other[, join, axis, level, …])用于将两个 Series 对齐,使其具有相同的索引Series.case_when(caselist)用于根据条件列表对 Series 中的元素进行条件判断并返回相应的值Series.drop([lab…...
SpringBoot项目注入 traceId 来追踪整个请求的日志链路
SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排查问题的时候,可以迅速根据 traceId 查找到相关请求的日志,特别是在生产环境的时候,用户可能只提供一个错误截图,我们作为开发…...
UVM 软链接应用
软链接在环境中主要是为了代码复用,目前用到的场景有两种: 1)将UT 环境的代码 链接到ST环境上复用: 将ut 的transaction和sequence等在ST上复用 使用方法 在st对应目录下执行命令: ln -s 。…/xxxx/UT/xxx/xx_transact…...
SCIKIT-LEARN 决策树实现csv文档简单的推论预测
一.学习背景 原文来自scikit-learn的学习拓展,根据樱花分类示例衍生而来。源文开源地址:scikit-learn: machine learning in Python — scikit-learn 0.16.1 documentation,想学机器学习和数据挖掘的可以去瞧瞧! 二.读取csv文档 …...
VM虚拟机安装与配置Ubuntu Linux操作系统详细教程~
一、下载VM虚拟机 VMware16.0.zip百度网盘下载链接:https://pan.baidu.com/s/1-l-CcAVNINqhRLSiQ26R7w?pwd=tznn 提取码: tznn 二、软件介绍 VMware(虚拟机)是指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统,通过它可在一台电脑上同…...
使用 Ray的可观察性功能监控和调试 Ray 应用程序和集群
一、引言 在分布式系统中,监控和调试是确保系统稳定运行的关键环节。Ray 作为一款高性能的分布式计算框架,提供了丰富的可观察性(Observability)功能,帮助用户监控和调试 Ray 应用程序和集群。本文将详细介绍如何使用 Ray 的可观察性功能,包括监控工具、调试流程、日志管…...
jmeter 如何做移动端的测试 特别是兼容性测试
JMeter本身主要是一款用于性能测试和功能测试的工具,虽然它并非专门为移动端测试设计,但可以通过一些方式来对移动端应用进行测试,以下从测试准备、测试过程及注意事项等方面为你详细介绍: 一、测试准备 (一)环境搭建 JMeter安装与配置:确保JMeter已经正确安装在测试机…...
模板方法模式
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架,允许子类在不改变算法结构的情况下重写某些步骤的具体实现。 核心思想 抽象类定义模板方法(final 修饰,防止子类修改…...
64位精度HPC计算引擎的十年博弈:AMD如何以性价比颠覆NVIDIA霸权?
若期望一款中央处理器(CPU)具备图形处理器(GPU)级别的浮点运算性能,根据CPU发展路线图,大约需等待六年左右。这显然是一段漫长的等待期,这也解释了为何十五年前众多高性能计算(HPC&a…...
2P4M-ASEMI照明控制专用2P4M
编辑:ll 2P4M外观与基本结构 2P4M 可控硅通常封装在一个小巧的塑料外壳内,从外观上看,它有着几个标志性的引脚。一般为三脚结构,每个引脚都肩负着不同的使命。其内部结构精密复杂,核心是由多层半导体材料交替堆叠而成…...
【工程管理与安全工程方向 EI会议征稿 | EI Compendex、Scopus收录】2025年工程管理与安全工程国际学术会议 (EMSE 2025)
重要信息: 大会官网:www.ic-emse.com【论文投稿】 大会时间:2025年3月21-23日 大会地点:中国-南京 截稿时间:以官网信息为准 出版信息:<...
VMware work station 与Device/Credential Guard不兼容
1.出现如下错误 按 下Windows徽标键R 然后输入msinfo32.exe,会出现系统信息,在系统信息里找到基于虚拟化的安全性,查看是否打开 gpedit.msc 注册表修改...
STM32开发学习(三)----使用STM32CUBEMX创建项目
前言 开始正式接触代码,学习代码开发,先熟悉STM32CUBEMX软件,控制开发板的GPIO。(STM32F103C8T6)。 正式开始 1.打开软件 2.点击ACCESS TO MCU SELECTOR,进入软件选择,可能会弹出更新,等待更新完成即可。…...
smolagents学习笔记系列(七)Examples-Self-correcting Text-to-SQL
这篇文章锁定官网教程中 Examples 章节中的 Self-correcting Text-to-SQL 文章,主要介绍了如何使用 Agent 对数据库进行查找。 官网链接:https://huggingface.co/docs/smolagents/v1.9.2/en/examples/text_to_sql; 【注意事项】:…...
ffmpeg常用方法(一)
FFmpeg是一个非常强大的开源项目,提供了一套可以用来录制、转换数字音频、视频,并能将其转换成不同格式的工具和库。它是命令行工具,意味着它没有图形用户界面,但它能够被嵌入到其他应用程序中。它支持多种操作系统,包…...
c++:多态
1.多态 多态就是多种形态的意思。 分为编译时多态,也叫静态多态,通过传递不同参数的方式使同一个函数好像实现了不同的状态。eg:函数模板,函数重载 还有运行时多态,也叫动态多态。通过使用不同的对象调用“同一个函数”…...
Nuxt.js 3【详解】服务器 Server
Nuxt.js 是一个全栈框架,可以在一个项目中,同时完成前端和后端的开发。 服务器架构 Nuxt.js 的服务端由 Nitro 实现,Nitro 由基于 H3 实现。 Nitro 官网 https://nitro.build/guideH3 官网 https://h3.unjs.io/guide 接口路由 基于文件目录自…...
DeepSeek回答:AI时代Go语言学习路线
最近有小伙伴经常会问:**该如何学习入门Go语言?怎样提升Go语言Coding水平?**这篇文章我们就使用DeepSeek来梳理下Go语言在AI时代的学习路线。 向DeepSeek提问的问题原文: 你现在是一名资深的Go语言工程师,精通Go语言并…...
OpenGL 04--GLSL、数据类型、Uniform、着色器类
一、着色器 在 OpenGL 中,着色器(Shader)是运行在 GPU 上的程序,用于处理图形渲染管线中的不同阶段。 这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器…...
仅需三分钟,使用Vue3.x版本组件式风格实现一个消息提示组件!
一、前言 在日常的前端项目开发中,我们时常需要使用到“消息提示”(以下简称“消息”)这个组件来帮助我们更好的给予用户提示,例如常见的“登录成功”、“操作成功”、“服务器异常”等等提示。 尽管市面上已经有一些组件库提供了…...
天猫代运营公司推荐:品融电商
天猫代运营公司推荐:品融电商 在电商行业竞争日益激烈的今天,选择一家专业的天猫代运营公司成为众多品牌商家提升市场竞争力、实现销售增长的关键。在众多代运营公司中,品融电商凭借其专业的团队、丰富的经验和显著的成功案例,脱…...
2025.2.26#Java开发中的鉴权机制详解
1、Java开发中的鉴权机制详解 用户问的是在Java开发中什么是鉴权。首先,我需要明确鉴权的定义。鉴权,也就是认证授权,是确认用户身份和权限的过程。可能用户刚接触安全相关的内容,需要简单明了的解释。 接下来,我应该分…...
AF3 DataPipeline类解读
AlphaFold3 的DataPipeline类处理单链蛋白数据,主要负责从不同数据源(FASTA、PDB、mmCIF、ProteinNet .core 文件等)解析输入序列、MSA、模板匹配、序列嵌入(如 ESM-2)、并将其转换为 AlphaFold3 可用的特征格式。 源代码: class DataPipeline:"""Assem…...
Windows系统PyTorch环境配置
0、前言 深度学习为什么要配置GPU? GPU(图形处理单元)最初是为图形渲染而设计的,它们擅长处理大量并行计算任务。深度学习模型,特别是卷积神经网络(CNN)和循环神经网络(RNN…...
策略模式环境类的实现方式对比
文章目录 1、策略模式2、聚合策略类实现方式一3、聚合策略类实现方式二4、对比5、补充:ApplicationContextAware接口 1、策略模式 近期工作中,需要处理4.x和5.x两个版本的数据,所以自然想到的是策略模式,写一个抽象类,…...
深入理解JVM的运行时数据区
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
mapbox基础,加载background背景图层
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象…...
14.二叉搜索树
二叉搜索树 1.概念 ⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树: *若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值 *若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点…...
javascript-es6 (五)
内置构造函数 在 JavaScript 中 最主要 的数据类型有 6 种: 基本数据类型: 字符串、数值、布尔、undefined、null 引用类型: 对象 但是,我们会发现有些特殊情况: //普通字符串 const str peiqi console.log(str.length) //…...
飞鱼科技游戏策划岗内推
协助策划完成相关工作,包括但不仅限于策划配置,资料搜集,游戏体验; 游戏策划相关作品;游戏大赛经历;游戏demo制作经历;游戏公司策划岗位实习经历优先 内推码 DSZP7YFU...
探索浮点数在内存中的存储(附带快速计算补码转十进制)
目录 一、浮点数在内存中的存储 1、常见的浮点数: 2、浮点数存储规则: 3、内存中无法精确存储: 4、移码与指数位E: 5、指数E的三种情况: 二、快速计算补码转十进制 1、第一种方法讨论: 2、第二种方…...
elfk+zookeeper+kafka数据流
申请7台部署elfkzookeeperkafka 数据流: filebeat(每台app) ------>【logstash(2) kafka(3)】 -------> logstash(1) -------> 【elasticsearch(3) kibana(1)】...
汽车悬架系统技术演进:从被动到全主动的革新之路(主动悬架类型对比)
在汽车工业的百年发展史中,悬架系统始终是平衡车辆性能与舒适性的关键战场。随着消费者对驾乘体验要求的不断提升,传统被动悬架已难以满足中高端车型的需求,而半主动与全主动悬架技术的崛起,正在重塑行业格局。本文将深入解析三大…...
什么限制了LLM:空间复杂度限制
什么限制了LLM: 空间复杂度限制 空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,它描述了算法所需的额外存储空间与输入数据规模之间的增长关系。这里的存储空间主要包括算法执行过程中所使用的变量、数据结构、栈空间等。和时间复杂度类似,空间复杂度通常也…...
Docker02 - 深入理解Docker
深入理解Docker 文章目录 深入理解Docker一:Docker镜像原理1:镜像加载原理1.1:unionFS1.2:加载原理 2:分层理解 二:容器数据卷详解1:什么是容器数据卷2:使用数据卷3:具名…...
深度学习中卷积层(Conv)、BN层(Batch Normalization)和 ReLU层(Rectified Linear Unit)的详细介绍
一、卷积层(Conv) 定义 卷积层是深度学习中卷积神经网络(CNN)的核心组成部分。它通过对输入数据(如图像)进行卷积操作来提取特征。卷积操作是用一个卷积核(也称为滤波器)在输入数据上…...
二分查找算法的全面解析C++
一、核心原理与特性 二分查找是一种**对数时间复杂度(O(log n))**的高效搜索算法46,需满足两个前提条件: 数据存储在连续内存空间(如数组)数据按升序/降序有序排列35 算法通过折半比较缩小搜索范围: 初始化左右边界…...