【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…

asp.net core mvc区域路由

ASP.NET Core 区域路由&#xff08;Area Routing&#xff09;是一种将应用程序中的路由划分为多个区域的方式&#xff0c;类似于 MVC 的控制器和视图的区域划分。区域路由可以帮助开发人员更好地组织应用程序的代码和路由&#xff0c;并使其更易于维护。 要使用区域路由&#…

小程序如何设置余额充值

在小程序中设置余额充值是一种非常有效的方式&#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; &变…

Hive 常见数据倾斜场景及解决方案(Map\Join\Reduce端)

目录 MapReduce流程简述a) Map倾斜b) Join倾斜c) Reduce倾斜 首先回顾一下MapReduce的流程 MapReduce流程简述 输入分片&#xff1a; MapReduce 作业开始时&#xff0c;输入数据被分割成多个分片&#xff0c;每个分片大小一般在 16MB 到 128MB 之间。这些分片会被分配给不同的…

移动手机截图,读取图片尺寸

这个代码的设计初衷是为了解决图片处理过程中的一些痛点。想象一下&#xff0c;我们都曾遇到过这样的情况&#xff1a;相机拍摄出来的照片、网络下载的图片&#xff0c;尺寸五花八门&#xff0c;大小不一。而我们又渴望将它们整理成一套拥有统一尺寸的图片&#xff0c;让它们更…

ChatGPT 调教指南:从 PDF 提取标题并保存

一、请使用python编写一段代码&#xff0c;使用pymupdf包从pdf中提取标题&#xff0c;保存标题名称和页数。 我没有加任何的答案提示&#xff0c;看看 GPT 如何反应。它应该是知道 PDF 没有任何语义信息&#xff0c;一切标题或者正文全是文本框。 好的&#xff0c;以下是使用py…

零基础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;包括太阳能、风能、潮汐能、地…

网络知识点之—PPPoE

本文章已收录至《网络》专栏&#xff0c;点进右上角专栏图标可访问本专栏 PPPoE&#xff08;Point-to-Point Protocol Over Ethernet&#xff09;&#xff0c;指以太网上的点对点协议&#xff0c;是将点对点协议&#xff08;PPP&#xff09;封装在以太网&#xff08;Ethernet&a…

机器学习的超参数 、训练集、归纳偏好

一、介绍 超参数&#xff08;Hyperparameters&#xff09;和验证集&#xff08;Validation Set&#xff09;是机器学习中重要的概念&#xff0c;用于调整模型和评估其性能。 超参数&#xff1a; 超参数是在机器学习模型训练过程中需要手动设置的参数&#xff0c;而不是从数据…

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

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

K8S-EverNote同步

Node污点 释义看文档就好 https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/taint-and-toleration/ 污点是Node的属性 容忍度是Pod的属性 用来标记各自特征的&#xff0c;通常协同工作。 举个例子&#xff0c; 一个Node的污点 kubectl taint nodes node1 key1v…

算法练习10——数组为空的最少操作次数

LeetCode 100032 使数组为空的最少操作次数 给你一个下标从 0 开始的正整数数组 nums 。 你可以对数组执行以下两种操作 任意次 &#xff1a; 从数组中选择 两个 值 相等 的元素&#xff0c;并将它们从数组中 删除 。 从数组中选择 三个 值 相等 的元素&#xff0c;并将它们从数…

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

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

C++ —— 单机软件加入Licence许可权限流程(附详细流程图、详细代码已持续更新..)

单机版许可证简介 笼统的说:实现一个生成授权Lic文件应用程序(我们使用),生成的Lic文件给应用程序(客户使用)启动时读取一下对比加密后的字符串或自定义格式的密钥判断是否正确。 单机版许可证执行流程 第一级比对:发布的加密许可证文件,该加密许可证文件仅可用使用的软…