C++第三十八弹---一万六千字使用红黑树封装set和map
✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】
目录
1、set/map基本结构
2、红黑树基本结构改造
3、红黑树的迭代器
4、set的模拟实现
5、map的模拟实现
6、完整代码
1、set/map基本结构
在封装set/map之前,我们先看看stl源码大致是如何实现的。
set基本结构

map基本结构
 
从上面两张图我们可以看到,set和map的底层都是红黑树,红黑树的模板参数有5个,我们先知道前面两个即可,第一个key_type指的是key值,value_type指的是"value"值,我们知道set只有key值,而map有key和value,那这里是怎么通过一个模板参数实现两个容器的呢?
答案是set传参时,第一个和第二个传的都是key值;map传参时,第一个传key值,第二个传pair<K,V>键值对。
为什么有了pair<K,V>键值对还需要key值呢?
因为查找的时候需要通过key值查找。

2、红黑树基本结构改造
红黑树结点结构
只需对有效数据进行修改,改为T类型的模板,根据需要实例化红黑树。
enum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;// set传key值,map传pair键值对Colour _col;RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}
}; 
红黑树结构
红黑树的基本结构只需对插入进行修改,增加拷贝构造,赋值操作符重载,析构函数即可。
插入函数
插入时直接插入T类型的data值,但是在查找插入的位置时,有两种情况,如果插入的值是key值时,可以直接通过data比较大小,但是插入pair<K,V>值时,需要通过该值的first成员比较大小,那么如何能够让两者统一呢?
此处的解决办法是使用仿函数,获取key值。
set仿函数
struct SetKeyOfT
{const K& operator()(const K& key){return key;// 直接返回key值}
}; 
map仿函数
struct MapKeyOfT
{const K& operator()(const pair<K,V>& kv){return kv.first;// 返回first成员}
}; 
插入函数
只需在比较时进行修改,使用仿函数,返回值为键值对,第一个成员为插入位置的迭代器,第二个成员为bool值,插入成功返回true,失败返回false。
pair<Iterator,bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;// 插入新结点,返回插入位置迭代器+truereturn make_pair(Iterator(_root),true);}KeyOfT kot;// 仿函数对象,获取key值Node* parent = nullptr;Node* cur = _root;while (cur){// kot对象取T类型中data对象的keyif (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}// 二叉搜索树默认不能冗余,因此相等则返回falseelse{// 冗余返回当前结点迭代器+falsereturn make_pair(Iterator(cur),false);}}cur = new Node(data);Node* newnode = cur;//存新插入节点的地址cur->_col = RED;if (kot(parent->_data) < kot(data)){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){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}// 叔叔不存在或叔叔为黑else{if (cur == parent->_left){//    g//  p   u//cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g//  p   u//    cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}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 // 叔叔不存在,或者存在且为黑{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色//      g//   u     p//            cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//		g//   u     p//      cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;// 返回插入节点位置迭代器+truereturn make_pair(Iterator(newnode), true);
}
 
拷贝构造
拷贝构造需要拷贝原结点的有效数据,结构的链接关系,结点的颜色。此处还有一个问题,如果自己实现了构造函数,编译器就不会自动生成默认构造,当使用无参构造红黑树时会报错,因此此处需要实现默认构造。
拷贝函数
Node* Copy(Node* root)
{if (root == nullptr)return nullptr;// 新创建结点,使用前序链接结点Node* newroot = new Node(root->_data);newroot->_col = root->_col;// 更新颜色newroot->_left = Copy(root->_left);// 左孩子不为空需要链接双亲结点if (newroot->_left)newroot->_left->_parent = newroot;newroot->_right = Copy(root->_right);// 右孩子不为空需要链接双亲结点if (newroot->_right)newroot->_right->_parent = newroot;return newroot;
}
 
拷贝构造函数
// 强制生成默认构造
RBTree() = default;// 拷贝构造 解决深拷贝问题,写了拷贝构造则不自动生成默认构造
RBTree(const RBTree<K, T, KeyOfT>& t)
{_root = Copy(t._root);
}
 
赋值操作符重载
赋值操作符重载的实现与stl容器类似,可以直接使用现代写法,交换地址即可,但是形参不能加const。
RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
{swap(_root, t._root);// 使用现代写法,交换地址即可return *this;
}
 
析构函数
析构函数将动态开辟的空间手动释放即可。
释放空间函数
void Destroy(Node* root) 
{if (root == nullptr)return;// 使用后序释放空间Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;// 手动置空
}
 
析构函数
~RBTree()
{Destroy(_root);_root = nullptr;// 手动将_root置空
}
 
3、红黑树的迭代器
 迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以前问题:
begin()与end()
 STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?此处为了简单实现红黑树,我们将end()给nullptr。
迭代器结点类
解引用,箭头,不等于这些函数重载与链表的迭代器实现类似,因此此处不做详细讲解,单独对前置++详细讲解,uu们可以自己分析实现后置++和--函数。
前置++的实现有两种情况:
- 1、当前结点的右子树不为空,下一个访问的结点为右子树的最左结点
 - 2、当前结点的右子树为空,下一个访问的结点为孩子是父亲的左祖先
 
右子树不为空

右子树为空

前置++
Self& operator++()
{// 右子树不为空 访问右子树的最左结点if (_node->_right){Node* leftMin = _node->_right;while (leftMin->_left){leftMin = leftMin->_left;}_node = leftMin;}// 右子树为空,找孩子是父亲的左祖先else{Node* cur = _node;Node* parent = cur->_parent;// 当前结点是最右结点时,parent为空while (parent && cur != parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;
}
 
迭代器完整代码
template<class T,class Ref,class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;Node* _node;typedef __RBTreeIterator<T, Ref, Ptr> Self;__RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++();
}; 
begin()和end()函数的获取
begin()在红黑树中最小节点(即最左侧节点)的位置。end()放在最大节点(最右侧节点)的下一个位置,此处给为nullptr。
实现上面的函数之前需要在红黑树类中typedef该迭代器。
typedef __RBTreeIterator<T, T&, T*> Iterator;// 普通迭代器
typedef __RBTreeIterator<T, const T&, const T*> ConstIterator;// const迭代器 
begin()函数的获取
begin()在红黑树中最小节点(即最左侧节点)的位置。
// 普通迭代器
Iterator Begin()
{Node* leftMin = _root;// leftMin可能为空while (leftMin && leftMin->_left){leftMin = leftMin->_left;}// 返回最左侧结点return Iterator(leftMin);
}
// const迭代器
ConstIterator Begin() const
{Node* leftMin = _root;// leftMin可能为空while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return ConstIterator(leftMin);
}
 
end()函数的获取
end()放在最大节点(最右侧节点)的下一个位置,此处给为nullptr。
// 结尾为空,开始为空时则不进入不等于的循环
Iterator End()
{return Iterator(nullptr);
}
ConstIterator End() const
{return ConstIterator(nullptr);
}
 
4、set的模拟实现
template<class K>
class set
{// set获取key值仿函数,传入红黑树第三个模板参数struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:// Iterator不知道是静态成员变量还是类型,使用typenametypedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}iterator find(const K& key){return _t.Find(key);}
private:// 加const 时key不能修改RBTree<K, const K, SetKeyOfT> _t;
};
 
set测试代码
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);
}
 
5、map的模拟实现
template<class K,class V>
class map
{struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};
public:// Iterator不知道是静态成员变量还是类型,使用typenametypedef typename RBTree<K, pair<const K,V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K,const V>, MapKeyOfT>::ConstIterator const_iterator;const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}pair<iterator, bool> insert(const pair<K,V>& kv){return _t.Insert(kv);}iterator find(const K& key){return _t.Find(key);}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.first->second;}
private:// pair加const,key不能修改RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
 
map测试代码
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;
}
 
6、完整代码
RBTree.h
#pragma once
#include<vector>enum 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 __RBTreeIterator
{typedef RBTreeNode<T> Node;Node* _node;typedef __RBTreeIterator<T, Ref, Ptr> Self;__RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){// 右子树不为空 访问右子树的最左结点if (_node->_right){Node* leftMin = _node->_right;while (leftMin->_left){leftMin = leftMin->_left;}_node = leftMin;}// 右子树为空,找孩子是父亲的左祖先else{Node* cur = _node;Node* parent = cur->_parent;// 当前结点是最右结点时,parent为空while (parent && cur != parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
};template<class K,class T,class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T, T&, T*> Iterator;typedef __RBTreeIterator<T, const T&, const T*> ConstIterator;ConstIterator Begin() const{Node* leftMin = _root;// leftMin可能为空while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return ConstIterator(leftMin);}ConstIterator End() const{return ConstIterator(nullptr);}Iterator Begin(){Node* leftMin = _root;// leftMin可能为空while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);}// 结尾为空,开始为空时则不进入不等于的循环Iterator End(){return Iterator(nullptr);}// 强制生成默认构造RBTree() = default;// 拷贝构造 解决深拷贝问题,写了拷贝构造则不自动生成默认构造RBTree(const RBTree<K, T, KeyOfT>& t){_root = Copy(t._root);}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){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{// 找到返回当前结点迭代器return Iterator(cur);}}// 没找到返回End()return End();}pair<Iterator,bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);// 根节点为黑色_root->_col = BLACK;// 插入新结点,返回插入位置迭代器+truereturn make_pair(Iterator(_root),true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){// 插入值更大则插入到右边// kot对象取T类型中Data对象的keyif (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}// 小则在左边else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}// 二叉搜索树默认不能冗余,因此相等则返回falseelse{// 冗余返回当前结点迭代器+falsereturn make_pair(Iterator(cur),false);}}// 为空则找到插入位置 需要先找到父亲的位置// key值大于父亲的值则在右侧cur = new Node(data);Node* newnode = cur;//存新插入节点的地址// 新增结点为红色cur->_col = RED;if (kot(parent->_data) < kot(data)){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){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}// 叔叔不存在或叔叔为黑else{if (cur == parent->_left){//    g//  p   u//cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//    g//  p   u//    cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}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 // 叔叔不存在,或者存在且为黑{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色//      g//   u     p//            cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//		g//   u     p//      cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;// 返回插入节点位置迭代器+truereturn make_pair(Iterator(newnode), true);}// 右单旋 左边较高void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;// 更新parentparent->_left = subLR;// subLR不为空则更新父亲if (subLR)subLR->_parent = parent;subL->_right = parent;//xxx// 提前存parent的父亲Node* ppNode = parent->_parent;parent->_parent = subL;// parent为根节点if (parent == _root){_root = subL;_root->_parent = nullptr;}else{// parent为ppNode的左结点if (parent == ppNode->_left){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}// 左单旋 右边较高void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;// 更新parentparent->_right = subRL;// subRL不为空if (subRL)subRL->_parent = parent;subR->_left = parent;// 提前存parent的父结点Node* ppNode = parent->_parent;parent->_parent = subR;// parent为根节点if (parent == _root){_root = subR;_root->_parent = nullptr;}else{// 左if (parent == ppNode->_right){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){// 1、判断根节点是否为黑if (_root->_col == RED){return false;}int refNum = 0;// 最左路径的黑色结点个数,参考数量Node* cur = _root;while (cur){if (cur->_col == BLACK){refNum++;}cur = cur->_left;}return Check(_root,0,refNum);}
private:void Destroy(Node* root) {if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}Node* Copy(Node* root){if (root == nullptr)return nullptr;// 新创建结点Node* newroot = new Node(root->_data);newroot->_col = root->_col;// 更新颜色newroot->_left = Copy(root->_left);if (newroot->_left)newroot->_left->_parent = newroot;newroot->_right = Copy(root->_right);if (newroot->_right)newroot->_right->_parent = newroot;return newroot;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}bool Check(Node* root,int blackNum,const int refNum){if (root == nullptr){// 2、判断是否存在相同数量的黑色节点路径if (blackNum != refNum){cout << "存在黑色结点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){// 3、判断是否存在连续的红色结点cout << root->_kv.first << "存在连续的红色结点" << endl;return false;}if (root->_col == BLACK)blackNum++;return Check(root->_left,blackNum,refNum)&& Check(root->_right, blackNum, refNum);}
private:Node* _root = nullptr;//size_t _size = 0;
}; 
Myset.h
#pragma once
#include "RBTree.h"namespace lin
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:// Iterator不知道是静态成员变量还是类型,使用typenametypedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}iterator find(const K& key){return _t.Find(key);}private:// 加const 时key不能修改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);s.insert(5);s.insert(7); set<int>::iterator it = s.begin();while (it != s.end()){//*it += 5;cout << *it << " ";++it;}cout << endl;//for (auto e : s)//{//	cout << e << endl;//}PrintSet(s);// 浅拷贝问题set<int> copy(s);PrintSet(copy);copy = s;PrintSet(copy);}
} 
Mymap.h
#pragma once
#include "RBTree.h"namespace lin
{template<class K,class V>class map{struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};public:// Iterator不知道是静态成员变量还是类型,使用typenametypedef typename RBTree<K, pair<const K,V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K,const V>, MapKeyOfT>::ConstIterator const_iterator;const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}pair<iterator, bool> insert(const pair<K,V>& kv){return _t.Insert(kv);}iterator find(const K& key){return _t.Find(key);}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.first->second;}private:// pair加const,key不能修改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;}
} 
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include "RBTree.h"
#include "Myset.h"
#include "Mymap.h"int main()
{//lin::test_set();lin::test_map2();return 0;
} 
相关文章:
C++第三十八弹---一万六千字使用红黑树封装set和map
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】 目录 1、set/map基本结构 2、红黑树基本结构改造 3、红黑树的迭代器 4、set的模拟实现 5、map的模拟实现 6、完整代码 1、set/map基本结构 在封装…...
★ C++基础篇 ★ vector 类
Ciallo~(∠・ω< )⌒☆ ~ 今天,我将继续和大家一起学习C基础篇第六章----vector类 ~ 目录 一 vector的介绍及使用 1.1 vector的介绍 1.2 vector的使用 1.2.1 vector的定义 1.2.2 vector iterator 的使用 1.2.3 vector 空间增长问题 1.2.4 vecto…...
原生js用Export2Excel导出excel单级表头和多级表头数据方式实现
原生js用Export2Excel导出excel单级表头和多级表头数据方式实现 原生js用Export2Excel导出excel单级表头和多级表头数据方式实现HTML文件导入需要的文件HTML文件中实现导出函数HTML总代码实现汇总(直接复制代码,注意js引入路径) 原生js用Expo…...
急需翻译PDF文件怎么办?pdf翻译在线快速帮你解决
面对满屏幕密密麻麻的pdf文件,我常常感到头疼! 语言障碍让我在获取信息的道路上踌躇不前,但自从我发现了pdf在线翻译成中文的神奇工具,一切问题似乎都迎刃而解。 这些软件不仅让我能够快速跨过语言壁垒,还让我在学术…...
线程安全的集合类和并发数据结构
在Java中,线程安全的集合类和并发数据结构对于处理多线程环境下的数据共享和同步至关重要。这些集合和数据结构通过不同的机制来确保在多线程环境下数据的一致性和完整性。以下是一些常见的线程安全的集合类和并发数据结构: 线程安全的集合类 Vector 描…...
Linux环境下运行介绍
1. 文件编程函数介绍 如果在Linux系统下学习C语言,就会了解到两套文件编程接口函数: C语言标准的文件编程函数: fopen、fread、fwrite、fclose Linux下提供的文件编程函数: open、read、write、close 传参的区别: 基于文件指针: fopen fclose fread…...
Adobe Media Encoder ME 2023-23.6.6.2 解锁版下载安装教程 (专业的视频和音频编码渲染工具)
前言 Adobe Media Encoder(简称Me)是一款专业的音视频格式转码软件,文件格式转换软件。主要用来对音频和视频文件进行编码转换,支持格式非常多,使用系统预设设置,能更好的导出与相关设备兼容的文件。 一、…...
在go语言里io.EOF怎么理解呢?
Go语言在处理文件和其他I/O流时,会使用io.EOF常量来表示文件结束(End Of File)的情况。 io.EOF是Go标准库中io包定义的一个错误值,用于在读取操作达到文件末尾时返回。它是处理文件读取和I/O操作时常见的错误类型之一。当读取操作…...
日常编码工作与提升式学习两不误
在快速迭代的编程世界中,程序员们不仅需要高效完成日常编码任务,还需不断学习新技术、深化专业知识,以应对日益复杂的项目挑战。然而,如何在繁忙琐碎的编码工作与个人成长之间找到平衡,是不少程序员都面临的一个难题。…...
推荐被Stars5.8k的Java框架RuoYi
一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适的。于是利用空闲休息时间开始自己写了一套后台系统。如此有了若依。她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,…...
聊聊适配器模式
目录 适配器模式概念 主要实现方式 主要组成 UML用例图 代码示例 生活场景 应用场景 适配器模式概念 适配器模式属于结构型设计模式,它的主要目的是将一个类的接口转换成客户端所期望的另一种接口形式,使得原本接口不兼容的类可以一起工作。 主…...
韩国服务器的性能如何提升
韩国服务器的性能可以通过硬件升级、网络优化、缓存优化和软件优化来提升。具体方法如下,rak小编为您整理发布韩国服务器的性能如何提升。 1. 硬件升级 CPU升级:选择高性能的多核处理器,可以显著提升计算速度和响应能力。 内存升级࿱…...
体育器材管理系统的设计与实现---附源码 76709
摘 要 本文介绍了一种基于Spring Boot框架的体育器材管理系统,该系统旨在优化学校或教育机构对体育器材的管理流程。通过集成Spring Boot、MySQL、MyBatis以及前端HTML、CSS、JavaScript等技术,实现了器材信息的录入、查询、修改,器材的借用…...
ArcEngine提取面要素公共边的实现方法
1、前言 很久没写ArcEngine的内容了,正好这次有同志提了一个问题:如何用ArcEngine实现批量提取面要素之间的公共边?捣鼓了半天总算是解决了,下面就来说一说解决思路。 2、ArcMap的实现方法 首先准备一份测试数据,如…...
高可用集群keepalived 原理+实战
keepalived 1.高可用集群1.1简介1.2原理1.3 集群类型1.4实现高可用1.5VRRP:Virtual Router Redundancy Protocol1.5.1 VRRP 相关术语1.5.2VRRP 相关技术 2.实验2.1keepalived环境部署2.2抢占模式和非抢占模式2.2.1非抢占模式2.2.2抢占延迟模式 preempt_delay 2.3VIP…...
保姆级教程,带你复现病理AI的经典模型CLAM(一)|项目复现·24-08-19
小罗碎碎念 推文概述 复现CLAM的第一期推文 通过这期推文你首先会学会如何在服务器端使用jupyter编程,比你用其他的编译器(例如PyCharm、VS)会更加的清晰,对新手也更友好。 接着我会介绍如何进行数据预处理,以及你应…...
数据可视化之旅,从数据洞察到图表呈现,可视化的产品设计
图表作为数据可视化的重要工具,是对原始数据进行深度加工与解读的有效手段,它助力我们洞悉数据背后的真相,使我们能更好地适应这个由数据驱动的世界。无论是工作汇报、项目实施、产品设计、后台界面还是数据大屏展示,图表都扮演着…...
ArrayList 和 LinkedList 的区别是什么
数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指…...
在Matlab中进行射频电路S、Z、Y、ABCD等参数的转换
在Matlab中进行射频电路S、Z、Y、ABCD等参数的转换 目录 在Matlab中进行射频电路S、Z、Y、ABCD等参数的转换1、转换案例-3dB电桥2、将转换结果应用到ADS中制造理想3dB电桥器件 在微带线的ABCD矩阵的推导、转换与级联-Matlab计算实例(S、Z、Y参数转换)中&…...
渗透实战——为喜欢的游戏“排忧解难”
本文仅用于技术研究学习,请遵守相关法律,禁止使用本文所提及的相关技术开展非法攻击行为,由于传播、利用本文所提供的信息而造成任何不良后果及损失,与本账号及作者无关。 资料查询来源- 安全社区与AI模型结合探索【文末申请免费…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...
水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...
