【STL】用一棵红黑树封装map和set

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:C++进阶
⭐代码仓库:C++进阶
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!

用一棵红黑树封装map和set

  • 一、红黑树原码
  • 二、红黑树模板参数控制
  • 三、红黑树结点当中存储的数据
  • 四、红黑树结点中仿函数
  • 五、正向迭代器
    • 1、框架+构造函数
    • 2、Ref
    • 3、Ptr
    • 4、重载operator==和operator!=
    • 5、operator++ 【重点讲解】
      • (1)情况1:当前结点的右子树有结点
      • (2)情况2:当前结点的右子树为空
      • (3)代码
    • 6、operator--
      • (1)情况1:当前节点的左子树有节点
      • (2)情况2:当前结点的左子树没结点
      • (3)代码
    • 7、正向迭代器在RBTree中的实现
      • (1)begin()
      • (2)end()
      • (3)代码
    • 8、缺陷和改进
  • 六、反向迭代器
    • 1、反向迭代器的实现
    • 2、RBTree中使用反向迭代器
  • 七、代码汇总


一、红黑树原码

传送

二、红黑树模板参数控制

set是Key模型的容器,map是key-value模型的容器,而我们是需要用一种模板来进行实现set和map,所以,我们使用了key-value模型来进行模拟实现set和map,我们将第二个模版参数改成class T,这样能够以示区分原来的红黑树的模版。

在这里插入图片描述

这个T既可能是K值,也可能是key与value共同构成的键值对,我们看一下set容器的实现形式:
在这里插入图片描述

我们再来写一下map的实现形式:

在这里插入图片描述

三、红黑树结点当中存储的数据

那我们红黑树中的结点也是需要进行一下更改的,我们看下图,MySet中第二个模板参数传到RBTree中是传的是Key,而MyMap中第二个模板参数传到RBTree中的是pair<K, V>,这是kv的键值对,而RBTreeNode中接收的是第二个模板参数,我们以下列一下K和T不同的模板形式:

set:K和T都代表着Key。
map:K代表键值Key,T代表键值对pair<K, V>。

所以我们只需要使用第二个模板参数T就可以实现红黑树结点根据不同的容器进行模板实例化,但有同学要问了,为什么我们不能直接在set和map中只使用一个模版参数T呢?而偏偏要使用两个模板参数K和T?
似乎是没什么问题的,对set来讲有没有这个第一个参数K都不影响的,而对于大部分的map情况中也是没有什么影响的,而具有最重大影响的是map的find和erase这两个接口函数,这两个接口函数是需要用到第一个参数K的,所以为了保持所有的接口都能用,只能委屈一下set,让set多一个模板参数K。

在这里插入图片描述

//红黑树结点的定义
template<class T>
struct RBTreeNode
{//构造函数RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}//三叉链RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;//存储的数据T _data;//结点的颜色int _col; //红/黑
};

四、红黑树结点中仿函数

一个很简单的现象,参数T:set的上层是K,直接能进行比较,而map上层是pair<K, V>,而我们进行比较map的时候是很难进行pair比较的,因为比较不了,所以我们需要将pair中的K特地摘出来进行比较,那么这个比较就需要通过一个仿函数进行摘出来K进行比较键值对。

仿函数:我们之前写过这种类似的仿函数模型,其实就是要实现一个operator()的功能,这个类就有了类似函数的行为,就是一个仿函数类了。

Map:
在这里插入图片描述

我们Map写了以后,我们思考一下Set写不写?
看似似乎并不用写这个Set,因为我们的Key能直接拿到,但是这只是我们人的视角下是肯定可以的,但是编译器能那么聪明吗?我们了解到,编译器是没有办法进行判断我们传过来的是Map还是Set,所以我们需要委屈一下Set,把Set的仿函数也书写一下:
小故事:今天女朋友说要去逛街买包,你敢不去吗?也就是Set是一个陪逛街的身份,但它必须陪着Map逛街。
在这里插入图片描述

在这里插入图片描述

这样我们根据上面的逻辑图看实例化,是在RBTree中加入一个仿函数,将Set和Map的仿函数比较过程进行传参并实例化,那我们下面用一个Find子函数进行解析一下:

	// 红黑树的查找Node* Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){// 当前结点的值大于寻找的结点的值if (key < kot(cur->_data)){cur = cur->_left;}else if (key > kot(cur->_data)){cur = cur->_right;}else{// 找到了return cur;}}return nullptr;}

在这里插入图片描述

五、正向迭代器

红黑树的正向迭代器实际上就是对结点指针进行了封装,因此在正向迭代器当中实际上就只有一个成员变量,那就是正向迭代器所封装结点的指针。

// 正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{typedef RBTreeNode<T> Node; //结点类型typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器类型Node* _node;
};

我们在之前写过有关T,Ref和Ptr,所以我们先介绍一下,T就是我们的模版参数,Ref是解引用操作,Ptr是指针操作。

1、框架+构造函数

// 正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{typedef RBTreeNode<T> Node; //结点类型typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器类型// 构造函数__TreeIterator(Node* node):_node(node){}Node* _node;
}

解释一下:T是第一个模版参数,定义类型的,Ref是解引用操作的模版参数,Ptr是指针指向的模版参数。

2、Ref

要对于一个结点解引用的操作,我们直接返回这个结点的值即可。

	// 正向迭代器解引用操作Ref operator*(){return _node->_data;}

3、Ptr

要对于一个结点进行->的操作的时候,我们直接返回这个结点数据的指针即可。

	// 正向迭代器指向操作Ptr operator->(){return &_node->_data;}

4、重载operator==和operator!=

直接使用Self类型进行判断这两个迭代器是否相同。

	// 判断两个正向迭代器是否不同bool operator!=(const Self& s) const{return _node != s._node;}// 判断两个正向迭代器是否相同bool operator==(const Self& s) const{return _node == s._node;}

5、operator++ 【重点讲解】

我们要实现这个operator++,我们首先需要了解这个红黑树的遍历打印是中序遍历打印,也就是先左子树,再根,再右子树,所以我们如下两种情况:

(1)情况1:当前结点的右子树有结点

找该节点的右子树的最左节点。

在这里插入图片描述

(2)情况2:当前结点的右子树为空

++操作后在该结点的祖先结点中,找到孩子不在父亲右的祖先。

在这里插入图片描述

(3)代码

	// 正向迭代器++操作Self operator++(){// 结点的右子树不为空,找右子树的最左结点if (_node->_right != nullptr){Node* R_left = _node->_right;while (R_left->_left != nullptr){R_left = R_left->_left;}_node = R_left;}// 结点的右子树为空,找祖先结点不为右的结点else{Node* cur = _node;Node* parent = cur->_parent;while (parent&& cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}

6、operator–

即遍历当前结点的前一个结点,与++正好相反。

1、如果当前结点的左子树不为空,则–操作后应该找到其左子树当中的最右结点。
2、如果当前结点的左子树为空,则–操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。

(1)情况1:当前节点的左子树有节点

–是找左子树的最右结点。
在这里插入图片描述

(2)情况2:当前结点的左子树没结点

在这里插入图片描述

(3)代码

	// 正向迭代器--操作Self operator--(){// 结点的左子树不为空,找左子树中的最右节点if (_node->_left != nullptr){Node* L_right = _node->_left;while (L_right->_right != nullptr){L_right = L_right->_right;}_node = L_right;}// 节点的左子树为空,找祖先结点中,找到孩子不在父亲左的祖先else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}

7、正向迭代器在RBTree中的实现

正向迭代器分为begin()和end(),所以我们分析一下begin()和end():

我们要将iterator放到public当中,让外部可以拿到。

(1)begin()

begin()就是我们前面所提的红黑树的第一个位置,而第一个位置则是整颗红黑树,begin函数返回中序序列当中第一个结点的正向迭代器,即最左结点。

	typedef __TreeIterator<T, T&, T*> iterator;// 最左节点iterator begin(){Node* left = _root;while (left && left->_left != nullptr){left = left->_left;}// 返回最左结点的迭代器return iterator(left);}

(2)end()

end()就是我们前面所提到的红黑树的最末尾的位置的下一个位置,那么就是nullptr!

	typedef __TreeIterator<T, T*, T&> iterator;// end()是整个树的最末尾结点的后一个位置iterator end(){return iterator(nullptr);}

(3)代码

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 != nullptr){left = left->_left;}// 返回最左结点的迭代器return iterator(left);}// end()是整个树的最末尾结点的后一个位置iterator end(){return iterator(nullptr);}
private:Node* _root;
};

8、缺陷和改进

我们大家不知道发现了没有,上述所写的end()是指向的最后一个结点的下一个位置,即nullptr,但我们的C++SGI版本中不是这样写的,其end()是指向最后一个元素的,所以我们来看一下SGI版本下的end()封装是怎么封装的:

在这里插入图片描述

我们看,在这个版本中,多了个header结点刚刚好是13根节点的父节点,这个header结点的左边指向这棵红黑树的最左节点象征着begin(),这个header结点的右边指向这棵红黑树的最右结点象征着rbegin()。实现end()和rend()时,直接用头结点构造出正向和反向迭代器即可。此后,通过对逻辑的控制,就可以实现end()进行–操作后得到最后一个结点的正向迭代器。

六、反向迭代器

1、反向迭代器的实现

反向迭代器我们根本都不需要一步一步写下来了,我们只需要用正向迭代器进行封装反向迭代器即可,如下代码:

在这里插入图片描述

//反向迭代器 -- 根据正向迭代器封装
template<class Iterator>
struct ReverseIterator
{typedef ReverseIterator<Iterator> Self; //反向迭代器的类型// 这里为了能够让反向迭代器能够拿到正向迭代器的解引用和指针typedef typename Iterator::reference Ref; //结点指针的解引用*typedef typename Iterator::pointer Ptr; //结点指针->//构造函数ReverseIterator(Iterator rit):_rit(rit){}// 和正向迭代器一样Ref operator*(){return *_rit;}// 和正向迭代器一样Ptr operator->(){return _rit.operator->(); }// ++操作就是正向迭代器的--操作Self& operator++(){--_rit;return *this;}// --操作就是正向迭代器的++操作Self& operator--(){++_rit;return *this;}// 不等号一样bool operator!=(const Self& s) const{return _rit != s._rit;}// 等号也一样bool operator==(const Self& s) const{return _rit == s._rit;}Iterator _rit;
};

2、RBTree中使用反向迭代器

template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;public:typedef __TreeIterator<T, T*, T&> iterator;typedef ReverseIterator<iterator> reverse_iterator;// 最左节点iterator begin(){Node* left = _root;while (left && left->_left != nullptr){left = left->_left;}// 返回最左结点的迭代器return iterator(left);}// end()是整个树的最末尾结点的后一个位置iterator end(){return iterator(nullptr);}// 最右结点reverse_iterator rbegin(){Node* right = _root;while (right && right->_left != nullptr){right = right->_right;}// 返回最右结点的迭代器return reverse_iterator(iterator(right));}// end()是整个树的最末尾结点的后一个位置reverse_iterator rend(){return reverse_iterator(iterator(nullptr));}
private:Node* _root;
};

七、代码汇总

Set.h:

#include"MyRBTree.h"namespace JRH
{template<class K, class T>class MySet{// 仿函数struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K, K, SetKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器// begin()iterator begin(){return _t.begin();}// end()iterator end(){return _t.end();}// rbegin()reverse_iterator rbegin(){return _t.rbegin();}// rend()reverse_iterator rend(){return _t.rend();}// 插入pair<iterator, bool> insert(const K& key){return _t.Insert(key);}// 删除void erase(const K& key){return _t.Erase(key);}// 查找iterator find(const K& key){return _t.Find(key);}private:RBTree<K, K, SetKeyOfT> _t;};
}

map.h

#include"MyRBTree.h"namespace JRH
{template<class K, class V>class MyMap{	//仿函数struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv) //返回键值对当中的键值Key{return kv.first;}};public:typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器// begin()iterator begin(){return _t.begin();}// end()iterator end(){return _t.end();}// rbegin()reverse_iterator rbegin(){return _t.rbegin();}// rend()reverse_iterator rend(){return _t.rend();}// 插入pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}// 删除void erase(const K& key){return _t.Erase(key);}// 查找iterator find(const K& key){return _t.Find(key);}//[]运算符重载函数V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));iterator it = ret.first;return it->second;}private:RBTree<K, pair<K, V>, MapKeyOfT> _t;};
}

正向和反向迭代器:

// 正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{typedef RBTreeNode<T> Node; //结点类型typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器类型// 为了让下面反向迭代器拿到typedef typename Ref reference;typedef typename Ptr pointer;// 构造函数__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 != nullptr){Node* R_left = _node->_right;while (R_left->_left != nullptr){R_left = R_left->_left;}_node = R_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 != nullptr){Node* L_right = _node->_left;while (L_right->_right != nullptr){L_right = L_right->_right;}_node = L_right;}// 节点的左子树为空,找祖先结点中,找到孩子不在父亲左的祖先else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}Node* _node;
};//反向迭代器 -- 根据正向迭代器封装
template<class Iterator>
struct ReverseIterator
{typedef ReverseIterator<Iterator> Self; //反向迭代器的类型// 这里为了能够让反向迭代器能够拿到正向迭代器的解引用和指针typedef typename Iterator::reference Ref; //结点指针的解引用*typedef typename Iterator::pointer Ptr; //结点指针->//构造函数ReverseIterator(Iterator rit):_rit(rit){}// 和正向迭代器一样Ref operator*(){return *_rit;}// 和正向迭代器一样Ptr operator->(){return _rit.operator->(); }// ++操作就是正向迭代器的--操作Self& operator++(){--_rit;return *this;}// --操作就是正向迭代器的++操作Self& operator--(){++_rit;return *this;}// 不等号一样bool operator!=(const Self& s) const{return _rit != s._rit;}// 等号也一样bool operator==(const Self& s) const{return _rit == s._rit;}Iterator _rit;
};

RBtree改装:

#include<algorithm>#include<iostream>
using namespace std;enum Col
{BLACK,RED
};//红黑树结点的定义
template<class T>
struct RBTreeNode
{//构造函数RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}//三叉链RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;//存储的数据T _data;//结点的颜色int _col; //红/黑
};template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;public:typedef __TreeIterator<T, T&,T*> iterator;typedef ReverseIterator<iterator> reverse_iterator;// 构造函数RBTree():_root(nullptr){}//赋值运算符重载RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t){swap(_root, t._root);return *this;}//析构函数~RBTree(){_Destroy(_root);_root = nullptr;}// 最左节点iterator begin(){Node* left = _root;while (left && left->_left != nullptr){left = left->_left;}// 返回最左结点的迭代器return iterator(left);}// end()是整个树的最末尾结点的后一个位置iterator end(){return iterator(nullptr);}// 最右结点reverse_iterator rbegin(){Node* right = _root;while (right && right->_left != nullptr){right = right->_right;}// 返回最右结点的迭代器return reverse_iterator(iterator(right));}// end()是整个树的最末尾结点的后一个位置reverse_iterator rend(){return reverse_iterator(iterator(nullptr));}// 红黑树的查找Node* Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){// 当前结点的值大于寻找的结点的值if (key < kot(cur->_data)){cur = cur->_left;}else if (key > kot(cur->_data)){cur = cur->_right;}else{// 找到了return cur;}}return nullptr;}// 插入pair<iterator, bool> Insert(const T& data){KeyOfT kot;// 一棵空树if (_root == nullptr){// 创建新结点 + 颜色初始化为黑色_root = new Node(data);_root->_col = BLACK; // 根节点得是黑的return make_pair(iterator(_root), true);}// 先找到 -- 利用二叉搜索树的方法进行查找Node* cur = _root;Node* parent = nullptr;// 左小右大while (cur){// 当前结点值大于待插入结点值,往左子树走if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}// 当前结点值小于待插入结点值,往右子树走else if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}// 待插入结点的值和当前结点的值相等,插入失败else{return make_pair(iterator(cur), false);}}// 将当前结点的值插入进去cur = new Node(data); // new一个新的结点cur->_col = RED;Node* newnode = cur; // 记录新插入的结点// 新插入的节点值小于父节点的节点值,插入到parent的左边if (kot(data) < kot(parent->_data)){parent->_left = cur;cur->_parent = parent;}// 新插入的节点值小于父节点的节点值,插入到parent的左边else{parent->_right = cur;cur->_parent = parent;}// 新插入结点的父节点是红色的,需要做出调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent; // parent是红色,则其父结点一定存在// 以grandparent左右孩子为分界线,分成if和elseif (parent == grandfather->_left) // 左孩子{Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)// 情况一:uncle存在且为红色{// 颜色调整grandfather->_col = RED;uncle->_col = parent->_col = BLACK;// 继续往上处理cur = grandfather;parent = cur->_parent;}else// 情况二:uncle存在且为黑色 / 情况三:uncle不存在{// 用左右孩子分为两半,一半是if用来表示在左孩子,一半是else用来表示在右孩子if (cur == parent->_left){//   g//  p// c// 右单旋RoateR(grandfather);// 颜色调整parent->_col = BLACK;grandfather->_col = RED;}else{//    g//  p//      c// 左右双旋RoateLR(grandfather);// 颜色调整cur->_col = BLACK;grandfather->_col = RED;}break;}}else // 右孩子{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED)// 情况一:uncle存在且为红色{// 颜色调整grandfather->_col = RED;uncle->_col = parent->_col = BLACK;// 继续往上处理cur = grandfather;parent = cur->_parent;}else// 情况二:uncle存在且为黑色 / 情况三:uncle不存在{// 用左右孩子分为两半,一半是if用来表示在左孩子,一半是else用来表示在右孩子if (cur == parent->_right){//   g//     p//      c// 左单旋RoateL(grandfather);// 颜色调整parent->_col = BLACK;grandfather->_col = RED;}else{//    g//      p//    c// 右左双旋RoateRL(grandfather);// 颜色调整cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}// 左单旋void RoateL(Node* parent){// 三叉链Node* subr = parent->_right;Node* subrl = subr->_left;Node* ppnode = parent->_parent;// subrl与parent的关系parent->_right = subrl;if (subrl)subrl->_parent = parent;// subl和parent的关系subr->_left = parent;parent->_parent = subr;// ppnode和subr的关系if (ppnode == nullptr){_root = subr;subr->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subr;}else{ppnode->_right = subr;}subr->_parent = ppnode;}}// 右单旋void RoateR(Node* parent){// 三叉链Node* subl = parent->_left;Node* sublr = subl->_right;Node* ppnode = parent->_parent;//sublr和parent之间的关系parent->_left = sublr;if (sublr)sublr->_parent = parent;//subl和parent的关系subl->_right = parent;parent->_parent = subl;//ppnode 和 subl的关系if (ppnode == nullptr){_root = subl;subl->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subl;}else{ppnode->_right = subl;}subl->_parent = ppnode;}}// 左右双旋void RoateLR(Node* parent){RoateL(parent->_left);RoateR(parent);}// 右左双旋void RoateRL(Node* parent){RoateR(parent->_right);RoateL(parent);}private://析构函数子函数void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}Node* _root;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/92034.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

玩转gpgpu-sim 04记—— __cudaRegisterBinary() of gpgpu-sim 到底做了什么

官方文档&#xff1a; GPGPU-Sim 3.x Manual __cudaRegisterBinary(void*) 被执行到的代码逻辑如下&#xff1a; void** CUDARTAPI __cudaRegisterFatBinary( void *fatCubin ) { #if (CUDART_VERSION < 2010)printf("GPGPU-Sim PTX: ERROR ** this version of GPGPU…

小程序如何设置余额充值

在小程序中设置余额充值是一种非常有效的方式&#xff0c;可以帮助商家吸引更多的会员并提高用户的消费频率。下面将介绍如何在小程序中设置余额充值并使用。 第一步&#xff1a;创建充值方案 在小程序管理员后台->营销管理->余额充值页面&#xff0c;添加充值方案。可…

Spark性能监测+集群配置

spark-dashboard 参考链接 架构图 Spark官网中提供了一系列的接口可以查看任务运行时的各种指标 运行 卸载docker https://blog.csdn.net/wangerrong/article/details/126750198 sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest…

Linux系统编程(七):线程同步

参考引用 UNIX 环境高级编程 (第3版)黑马程序员-Linux 系统编程 1. 同步概念 所谓同步&#xff0c;即同时起步、协调一致。不同的对象&#xff0c;对 “同步” 的理解方式略有不同 设备同步&#xff0c;是指在两个设备之间规定一个共同的时间参考数据库同步&#xff0c;是指让…

小白继续深入学习C++

第1节 指针的基本概念 1、变量的地址&#xff1a; 变量是内存地址的简称&#xff0c;在C中&#xff0c;每定义一个变量&#xff0c;系统就会给变量分配一块内存&#xff0c;内存是有地址的。 C用运算符&获取变量在内存中的起始地址。 语法&#xff1a; &变…

零基础Linux_9(进程)环境变量+进程地址空间+进程创建fork

目录 1. 环境变量 1.1 环境变量基本概念 1.2 环境变量PATH 1.3 环境变量HOME和SHELL 1.4 获取环境变量&#xff08;main函数参数&#xff09; 1.4.1 main函数第三个参数 1.4.2 设置普通变量和环境变量 1.4.3 main函数前两个参数 2. 进程地址空间 2.1 验证进程地址空…

PHP8的数据封装(数据隐藏)-PHP8知识详解

面向对象的特点之一就是封装性&#xff0c;也就是数据封装&#xff0c;也被称为数据隐藏。 php8通过限制访问权限来实现数据的封装性&#xff0c;这里用到了public、private、protected、static和final几个关键字。下面来介绍前3个。 1.、public&#xff08;公共成员&#xf…

CSP-J第二轮试题-2021年-3题

文章目录 参考&#xff1a;总结 [CSP-J 2021] 网络连接题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 样例 #3样例输入 #3样例输出 #3 样例 #4样例输入 #4样例输出 #4 提示答案1答案2 现场真题注意事项 参考&#xff1a; https://www…

2022年全球一次能源消费量:石油消耗量持续增加达190.69百亿亿焦耳,亚太地区消费量居首位[图]

一次性能源是指从自然界取得未经改变或转变而直接利用的能源。如原煤、原油、天然气、水能、风能、太阳能、海洋能、潮汐能、地热能、天然铀矿等。一次性能源又分为可再生能源和不可再生能源&#xff0c;前者指能够重复产生的天然能源&#xff0c;包括太阳能、风能、潮汐能、地…

C/C++与汇编混合编程

1. C/C调用汇编 C/C想调用汇编代码必须要注意名称修饰的问题 名称修饰(name decoration): 一种标准的C/C编译技术, 通过添加字符来修改函数名, 添加的字符指明了每个函数参数的确切类型。主要是为了支持函数重载, 但对于汇编来说其问题在于, C/C编译器让链接器去找被修饰过的名…

cadence SPB17.4 S032 - 使用room来放置元件

文章目录 cadence SPB17.4 S032 - 使用room来放置元件概述笔记在orcad中设置子原理图的ROOM号码在空的Allegro工程中, 放入板框在allegro中建立room备注补充 - ROOM还得留着END cadence SPB17.4 S032 - 使用room来放置元件 概述 如果在allegro中直接手工或自动放置元件, 放好…

LeNet网络复现

文章目录 1. LeNet历史背景1.1 早期神经网络的挑战1.2 LeNet的诞生背景 2. LeNet详细结构2.1 总览2.2 卷积层与其特点2.3 子采样层&#xff08;池化层&#xff09;2.4 全连接层2.5 输出层及激活函数 3. LeNet实战复现3.1 模型搭建model.py3.2 训练模型train.py3.3 测试模型test…

Linux系统编程系列之进程间通信-信号量组

一、什么是信号量组 信号量组是信号量的一种&#xff0c; 是system-V三种IPC对象之一&#xff0c;是进程间通信的一种方式。 二、信号量组的特性 信号量组不是用来传输数据的&#xff0c;而是作为“旗语”&#xff0c;用来协调各进程或者线程工作的。信号量组可以一次性在其内…

【LeetCode】滑动窗口妙解无重复字符的最长子串

Problem: 3. 无重复字符的最长子串 文章目录 思路算法原理分析暴力枚举 哈希表滑动窗口 复杂度Code 思路 首先我们来分析一下本题的思路 如果读者有看过 长度最小的子数组 的话就可以清楚这个子串其实和子数组是一个道理&#xff0c;都是 连续的一段区间但是呢它们本质上还是存…

应用架构的演进:亚马逊的微服务实践

当你在亚马逊上购物时,或许不会想到,你看到的这个购物网站,其背后技术架构经历了什么样的变迁与升级。 还记得上世纪 90 年代,那个只卖书的网上书店吗?那时的亚马逊,不过是一个架构简单的网站,所有的功能都堆积在一个庞大的软件堡垒里。随着更多业务的增加、更新和迭代,这个软…

【小程序 - 基础】页面导航、页面事件、生命周期、WXS脚本_04

目录 一、页面导航 1. 什么是页面导航 2. 小程序中实现页面导航的两种方式 2.1 声明式导航 2.1.1 导航到 tabBar 页面 2.1.2 导航到非 tabBar 页面 2.1.3 后退导航 2.2 编程式导航 2.2.1 导航到 tabBar 页面 2.2.2 导航到非 tabBar 页面 2.2.3 后退导航 2.3. 导航…

从1开始的Matlab(快速入门)

MATLAB软件版本&#xff1a;MATLAB R2016b 本文是博主从零开始学Matlab的记录&#xff0c;适合第一次接触Matlab的同学阅读。 一、基础介绍 1.1界面认识 1.2变量命名 注&#xff1a;Matlab中的注释 %% 独占一行的注释&#xff08;有上下横线分割&#xff09; % 普通注释 …

C语言:选择+编程(每日一练Day9)

目录 选择题&#xff1a; 题一&#xff1a; 题二&#xff1a; 题三&#xff1a; 题四&#xff1a; 题五&#xff1a; 编程题&#xff1a; 题一&#xff1a;自除数 思路一&#xff1a; 题二&#xff1a;除自身以外数组的乘积 思路二&#xff1a; 本人实力有限可能对…

深入理解 Swift 新并发模型中 Actor 的重入(Reentrancy)问题

问题现象 我们知道,Swift 5.5 引入的新并发模型极大简化了并行逻辑代码的开发,更重要的是:使用新并发模型中的 Actor 原语可以大大降低并发数据竞争的可能性。 不过,即便 Actor 有如此神奇之功效,它也不是“万能药”,仍不能防止误用带来的问题。比如:Actor 重入(Reen…

283. 多边形,《算法竞赛进阶指南》,

283. 多边形 - AcWing题库 “多边形游戏”是一款单人益智游戏。 游戏开始时&#xff0c;给定玩家一个具有 N 个顶点 N 条边&#xff08;编号 1∼N&#xff09;的多边形&#xff0c;如图 1 所示&#xff0c;其中 N4 每个顶点上写有一个整数&#xff0c;每个边上标有一个运算符…