红黑树封装map和set

红黑树源代码

我们将由下列的KV模型红黑树来模拟封装STL库中的map和set

注意:为了实现封装map和set,我们需要对下列源码进行优化。

#pragma once
#include<iostream>
using namespace std;
//枚举类型的颜色分类
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;Colour _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://中序遍历副函数void Inorder(){_Inorder(_root);}//中序遍历主函数void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}//插入函数bool insert(const pair<K, V>& kv){//按照二叉树搜索树插入if (_root == nullptr)//根结点为空时new一个最初的根结点{_root = new Node(kv);_root->_col = BLACK;//根结点一定为黑return true;}Node* parent = nullptr;//这个为当前指针cur的父结点指针Node* cur = _root;//当前指针指向根while (cur)//当不为空,说明存在值,那么继续搜索可插入的地方{if (cur->_kv.first < kv.first)//key大于结点值,往右走{parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first)//key小于结点值,往左走{parent = cur;cur = cur->_left;}else//相等,那么不插入,插入失败{return false;}}cur = new Node(kv);//新增结点cur->_col = RED;//默认红色//插入if (parent->_kv.first > kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//开始判断颜色while (parent != nullptr && parent->_col == RED){Node* grandfather = parent->_parent;//如果父亲为红,那么违反红红规则,开始判断情况if (parent != nullptr && parent == grandfather->_left){Node* uncle = grandfather->_right;//记录叔叔结点if (uncle != nullptr && uncle->_col == RED)//如果叔叔存在或者为红色,情况一{//变色parent->_col = uncle->_col = BLACK;//父亲和叔叔都变黑grandfather->_col = RED;//爷爷变红//将cur和parent往上移继续判断cur = grandfather;parent = cur->_parent;}else//叔叔不存在或者存在且为黑色,情况二和情况三结合{if (cur == parent->_left){RotateR(grandfather);//右旋parent->_col = BLACK;grandfather->_col = RED;}else{RotateLR(grandfather); //左右双旋grandfather->_col = RED;cur->_col = BLACK;}break;//根结点为黑,不需要往上了}}else//parent在grandfather的右边{Node* uncle = grandfather->_left;//记录叔叔结点if (uncle != nullptr && uncle->_col == RED)//如果叔叔存在或者为红色,情况一{parent->_col = uncle->_col = BLACK;//父亲和叔叔都变黑grandfather->_col = RED;//爷爷变红//向上调整cur = grandfather;parent = grandfather->_parent;}else//叔叔不存在或者存在且为黑色,情况二和情况三结合{if (cur == parent->_left)//如果插入在parent的左边{RotateRL(grandfather);//右左双旋cur->_col = BLACK;grandfather->_col = RED;}else//如果插入在parent的右边{RotateL(grandfather);//左旋grandfather->_col = RED;parent->_col = BLACK;}break;//根结点为黑,不需要往上了}}}_root->_col = BLACK;//往上移动后无论cur是否为根结点,统一为改黑return true;//插入成功}//左单旋void RotateL(Node* parent){//定义新指针,方便操作Node* subR = parent->_right;Node* subRL = subR->_left;Node* pp = parent->_parent;//方便更改_root的操作parent->_right = subRL;//让parent结点链接subRLsubR->_left = parent;//让subR的左子树链接parentparent->_parent = subR;//由于parent的_parent由nullptr变成了subR,所以也需要重新链接if (subRL)//判断subRL是否为空,如果为空的话就不需要对subRL进行操作了,不然会出现对空指针进行解引用的问题{subRL->_parent = parent;//不为空,那么让subRL链接parent}if (pp == nullptr)//如果parent是整棵树的根结点{_root = subR;//subR变为根结点subR->_parent = nullptr;//subR的_parent为空}else//如果parent不是整棵树的根结点,那么将新的parent重新链接上一个结点{if (pp->_left = parent)//如果parent是上一个结点的左子树,那么新的parent也是{pp->_left = subR;}else//如果parent是上一个结点的右子树,那么新的parent也是{pp->_right = subR;}subR->_parent = pp;//更新subR的父结点}//parent->_bf = subR->_bf = 0;//由于旋转后,整棵树的高度变回插入前的,那么此时parent和subR(cur)的因子都变回0}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subRR = subL->_right;Node* pp = parent->_parent;//建立subL和parent之间的关系parent->_left = subRR;subL->_right = parent;//建立parent和subRR之间的关系parent->_parent = subL;if (subRR != nullptr){subRR->_parent = parent;}//建立PP和subL之间的关系if (pp == nullptr){_root = subL;subL->_parent = nullptr;}else{if (pp->_left == parent){pp->_left = subL;}else{pp->_right = parent;}subL->_parent = pp;}//更新平衡因子//subL->_bf = parent->_bf = 0;}//左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//if (bf == 0)//{//    //subLR自己就是新增//    subLR->_bf = 0;//    subL->_bf = 0;//    parent->_bf = 0;//}//else if (bf == -1)//{//    //subLR的左子树新增//    subLR->_bf = 0;//    subL->_bf = 0;//    parent->_bf = 1;//}//else if (bf == 1)//{//    //subLR的右子树新增//    subLR->_bf = 0;//    subL->_bf = -1;//    parent->_bf = 0;//}//else//{//    assert(false);//}}//右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);//if (bf == 0)//{//    //subRL自己就是新增//    parent->_bf = subR->_bf = subRL->_bf = 0;//}//else if (bf == -1)//{//    //subRL的左子树新增//    parent->_bf = 0;//    subRL->_bf = 0;//    subR->_bf = 1;//}//else if (bf == 1)//{//    //subRL的右子树新增//    parent->_bf = -1;//    subRL->_bf = 0;//    subR->_bf = 0;//}//else//{//    assert(false);//}}// blacknum是根结点到当前结点的黑色结点数量bool check(Node* root,int blacknum,int count){if (root == nullptr){if(count != blacknum){cout << "黑色结点数量不等" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色结点" << endl;return false;}if (root->_col == BLACK){++blacknum;}return check(root->_left,blacknum,count) && check(root->_right,blacknum,count);}bool isbalance(){if (_root == nullptr){return true;}if (_root->_col == RED){return false;}//找最左路径作为黑色结点数目的参考值Node* cur = _root;int count = 0;while (cur){if (cur->_col == BLACK)++count;cur = cur->_left;}int blacknum = 0;return check(_root,blacknum,count); }
private:Node* _root = nullptr;
};

红黑树的模板

首先,我们要知道,set是K模型,map是KV模型,那么如何用一颗红黑树同时封装出两种模型呢?这时候就应该想到模板了。

我们需要对红黑树类模板进行修改:

这是原始代码:

template<class K, class V>
class RBTree

 这是修改后的代码:

template<class K, class T, class KeyofT>
class RBTree

可以看到,这将V变为T,然后多出了KeyofT,这是什么意思呢?

class V    --->    class T

set是K模型,map是KV模型

在set容器中,T是对应着key:

    template<class K>class set{public://...private:RBTree<K, K, SetKeyofT> _t;};

在map容器中,T是对应着key和value组成的键值对:

    template<class K,class V>class map{public://...private:RBTree<K, pair<K, V>, MapKeyofT> _t;};

 

这时候会有一个疑问,能不能把第一个参数:class K 给去掉,因为在set中,K已经重复了;在map中,貌似键值对里也已经包含着key和value了?

可以肯定地说:不能去掉!在set中,确实可以省略不要;但是在map中,有些容器接口需要给出key的类型,那么在键值对中,我们只能取到key的值。所以在map中不能删去。

所以,我们更改了红黑树源码中的结点类实现

结点类实现

//定义一个结构体结点
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){}
};

可以看到,这里的 T _data就是新增的,T对应set中的key,map中的键值对。

  map、set中的仿函数

class KeyofT

 在上述set和map容器的私有成员中,分别有着:

 在map和set分别传入底层红黑树时,T传入的可能是key,也可能是key和value的键值对,如果是键值对,那么就需要将键值对的key提取出来再进行比较,那么此时就需要用到仿函数。

map容器:

    template<class K,class V>class map{public:struct MapKeyofT{const K& operator()(const pair<K,V>& kv){return kv.first;}};private:RBTree<K, pair<K, V>, MapKeyofT> _t;};

set容器:

    template<class K>class set{public:struct SetKeyofT{const K& operator()(const K& key){return key;}};private:RBTree<K, K, SetKeyofT> _t;};

可以看到,我们在这个仿函数中重载了operator(), 这个operator()在map中用来提取kv.first,也就是key值,为了能统一map和set,我们在set也重载了operator()。

所以set传入底层红黑树就是set的仿函数,map传入底层红黑树就是map的仿函数。

那么应该怎么用呢?

例如在insert插入函数中,我们需要获取结点内的key值进行比较大小

新定义了一个kot:

KeyofT kot;

这是其中一小段代码:

if (kot(cur->_data) < kot(data))//key大于结点值,往右走

这里可以调用仿函数,如果_data/data类型是key,那么在仿函数中直接就返回key;如果类型是键值对,那么就返回kv.first

迭代器类实现

在迭代器中,解引用操作就是获取结点的数据的引用,->操作就是获取结点数据的地址即可。

真正有难度的是++和--:

在树中的++和--是按照中序遍历的方式进行的,也就是让指针按照 左子树-根-右子树 的顺序移动。

在++操作中:

如果右子树不为空,那么进入右子树然后寻找最左结点。

如果右子树为空,那么往上返回父亲节点,然后找到当前结点不是父亲结点的右子树的父亲节点。

在--操作中:

如果左子树不为空,那么进入右子树然后寻找最右结点。

如果左子树为空,那么往上返回父亲节点,然后找到当前结点不是父亲结点的左子树的父亲节点。

//迭代器类
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 &_node->_data;//返回结点数据的地址}Self operator++(){if (_node->_right)//如果右子树存在{Node* cur = _node->_right;//则进入右子树while (cur->_left)//找到右子树的最左结点{cur = cur->_left;}_node = cur;//更新当前结点}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;}bool operator!=(const Self& s) const{return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个}bool operator==(const Self& s) const{return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个}
};

迭代器函数实现 

在树中的begin()和end()也有着不同的规则:

begin()获取的是树的最左结点。

end()获取的是树的最右结点的后一个结点,也就是空结点。

    //迭代器函数typedef _TreeIterator<T,T&,T*> iterator;typedef _TreeIterator<T, const T&, const T*> const_iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left)//寻找最左结点{cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* cur = _root;while (cur && cur->_left)//寻找最左结点{cur = cur->_left;}return iterator(cur);}const_iterator end() const{return iterator(nullptr);}

优化后的红黑树源码

#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;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;Node* _node;_TreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self operator++(){if (_node->_right){Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}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;}bool operator!=(const Self& s) const{return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个}
};//红黑树类
template<class K, class T, class KeyofT>
class RBTree
{typedef RBTreeNode<T> Node;
public://中序遍历副函数void Inorder(){_Inorder(_root);}//中序遍历主函数void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}//迭代器函数typedef _TreeIterator<T,T&,T*> iterator;typedef _TreeIterator<T, const T&, const T*> const_iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}const_iterator end() const{return iterator(nullptr);}//插入函数pair<iterator,bool> insert(const T& data){//按照二叉树搜索树插入if (_root == nullptr)//根结点为空时new一个最初的根结点{_root = new Node(data);_root->_col = BLACK;//根结点一定为黑return make_pair(iterator(_root),true);}Node* parent = nullptr;//这个为当前指针cur的父结点指针Node* cur = _root;//当前指针指向根KeyofT kot;while (cur)//当不为空,说明存在值,那么继续搜索可插入的地方{if (kot(cur->_data) < kot(data))//key大于结点值,往右走{parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data))//key小于结点值,往左走{parent = cur;cur = cur->_left;}else//相等,那么不插入,插入失败{return make_pair(iterator(cur), false);}}cur = new Node(data);//新增结点Node* newnode = cur;cur->_col = RED;//默认红色//插入if (kot(parent->_data) > kot(data)){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//开始判断颜色while (parent != nullptr && parent->_col == RED){Node* grandfather = parent->_parent;//如果父亲为红,那么违反红红规则,开始判断情况if (parent != nullptr && parent == grandfather->_left){Node* uncle = grandfather->_right;//记录叔叔结点if (uncle != nullptr && uncle->_col == RED)//如果叔叔存在或者为红色,情况一{//变色parent->_col = uncle->_col = BLACK;//父亲和叔叔都变黑grandfather->_col = RED;//爷爷变红//将cur和parent往上移继续判断cur = grandfather;parent = cur->_parent;}else//叔叔不存在或者存在且为黑色,情况二和情况三结合{if (cur == parent->_left){RotateR(grandfather);//右旋parent->_col = BLACK;grandfather->_col = RED;}else{RotateLR(grandfather); //左右双旋grandfather->_col = RED;cur->_col = BLACK;}break;//根结点为黑,不需要往上了}}else//parent在grandfather的右边{Node* uncle = grandfather->_left;//记录叔叔结点if (uncle != nullptr && uncle->_col == RED)//如果叔叔存在或者为红色,情况一{parent->_col = uncle->_col = BLACK;//父亲和叔叔都变黑grandfather->_col = RED;//爷爷变红//向上调整cur = grandfather;parent = grandfather->_parent;}else//叔叔不存在或者存在且为黑色,情况二和情况三结合{if (cur == parent->_left)//如果插入在parent的左边{RotateRL(grandfather);//右左双旋cur->_col = BLACK;grandfather->_col = RED;}else//如果插入在parent的右边{RotateL(grandfather);//左旋grandfather->_col = RED;parent->_col = BLACK;}break;//根结点为黑,不需要往上了}}}_root->_col = BLACK;//往上移动后无论cur是否为根结点,统一为改黑return make_pair(iterator(newnode), true);//插入成功}//左单旋void RotateL(Node* parent){//定义新指针,方便操作Node* subR = parent->_right;Node* subRL = subR->_left;Node* pp = parent->_parent;//方便更改_root的操作parent->_right = subRL;//让parent结点链接subRLsubR->_left = parent;//让subR的左子树链接parentparent->_parent = subR;//由于parent的_parent由nullptr变成了subR,所以也需要重新链接if (subRL)//判断subRL是否为空,如果为空的话就不需要对subRL进行操作了,不然会出现对空指针进行解引用的问题{subRL->_parent = parent;//不为空,那么让subRL链接parent}if (pp == nullptr)//如果parent是整棵树的根结点{_root = subR;//subR变为根结点subR->_parent = nullptr;//subR的_parent为空}else//如果parent不是整棵树的根结点,那么将新的parent重新链接上一个结点{if (pp->_left = parent)//如果parent是上一个结点的左子树,那么新的parent也是{pp->_left = subR;}else//如果parent是上一个结点的右子树,那么新的parent也是{pp->_right = subR;}subR->_parent = pp;//更新subR的父结点}//parent->_bf = subR->_bf = 0;//由于旋转后,整棵树的高度变回插入前的,那么此时parent和subR(cur)的因子都变回0}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subRR = subL->_right;Node* pp = parent->_parent;//建立subL和parent之间的关系parent->_left = subRR;subL->_right = parent;//建立parent和subRR之间的关系parent->_parent = subL;if (subRR != nullptr){subRR->_parent = parent;}//建立PP和subL之间的关系if (pp == nullptr){_root = subL;subL->_parent = nullptr;}else{if (pp->_left == parent){pp->_left = subL;}else{pp->_right = parent;}subL->_parent = pp;}//更新平衡因子//subL->_bf = parent->_bf = 0;}//左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//if (bf == 0)//{//    //subLR自己就是新增//    subLR->_bf = 0;//    subL->_bf = 0;//    parent->_bf = 0;//}//else if (bf == -1)//{//    //subLR的左子树新增//    subLR->_bf = 0;//    subL->_bf = 0;//    parent->_bf = 1;//}//else if (bf == 1)//{//    //subLR的右子树新增//    subLR->_bf = 0;//    subL->_bf = -1;//    parent->_bf = 0;//}//else//{//    assert(false);//}}//右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);//if (bf == 0)//{//    //subRL自己就是新增//    parent->_bf = subR->_bf = subRL->_bf = 0;//}//else if (bf == -1)//{//    //subRL的左子树新增//    parent->_bf = 0;//    subRL->_bf = 0;//    subR->_bf = 1;//}//else if (bf == 1)//{//    //subRL的右子树新增//    parent->_bf = -1;//    subRL->_bf = 0;//    subR->_bf = 0;//}//else//{//    assert(false);//}}// blacknum是根结点到当前结点的黑色结点数量bool check(Node* root, int blacknum, int count){if (root == nullptr){if (count != blacknum){cout << "黑色结点数量不等" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色结点" << endl;return false;}if (root->_col == BLACK){++blacknum;}return check(root->_left, blacknum, count) && check(root->_right, blacknum, count);}bool isbalance(){if (_root == nullptr){return true;}if (_root->_col == RED){return false;}//找最左路径作为黑色结点数目的参考值Node* cur = _root;int count = 0;while (cur){if (cur->_col == BLACK)++count;cur = cur->_left;}int blacknum = 0;return check(_root, blacknum, count);}
private:Node* _root = nullptr;
};

用红黑树封装set的容器

#pragma once
#include"RBTree.h"namespace bear
{template<class K>class set{public://仿函数struct SetKeyofT{const K& operator()(const K& key){return key;}};//红黑树最好不要修改,将普通迭代器和const迭代器都用const迭代器重命名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){return _t.insert(key);}private:RBTree<K, K, SetKeyofT> _t;};
}

 用红黑树封装map的容器

在map中,还需要对[]运算符进行重载。

#pragma once
#include"RBTree.h"namespace bear
{template<class K,class V>class map{public:struct MapKeyofT{const K& operator()(const pair<K,V>& kv){return kv.first;}};typedef typename RBTree<K, pair<K, V>, MapKeyofT>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> Insert(const pair<K, V>& kv){return _t.insert(kv);}private:RBTree<K, pair<K, V>, MapKeyofT> _t;};
}

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

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

相关文章

Markdown魔法手册:解锁高效写作的新技能

边使用边更新0.0... 文章目录 一、如何在Markdown中插入表情&#xff1f;二、文字样式设置1.文本颜色设置2.文本字号设置3.文本字体设置4. 实战演练5.黄色高亮 一、如何在Markdown中插入表情&#xff1f; 在Markdown中插入表情&#xff08;emoji&#xff09;的方法取决于你使用…

如何提升百度小程序的收录?百度小程序如何做优化?

​ 如何通过百度小程序获得更多的自然流量&#xff1f;这是做百度小程序肯定要考虑的问题&#xff0c;做百度小程序的目的就是想借助百度生态&#xff0c;做相应的关键词给自己的小程序引流&#xff0c;如何把流量给做起来呢&#xff0c;接下来我从不同的方面给大家进行分析讲解…

最新ChatGpt Desktop for Mac 安装使用教程

1. 下载地址 请点击链接下载 ChatGPT Desktop for MacOS 2. 使用要求 MacOS 版本 14需要时M1芯片的&#xff0c;如果你是因特尔的暂时还还不行 就算下载了也会出现下面的异常 3. 获取权限资格 目前 ChatGPT MacOS Desktop还不是全量开放的, 如果你没有收到通知说明你还没…

ipa 覆盖算法测试

相关文章 ipa 功能包测试 ipa 分区算法 ipa 分区算法总结&#xff0c;部分算法图解 ipa 覆盖算法分析&#xff08;一&#xff09; ipa 覆盖算法分析&#xff08;二&#xff09; 测试 网上找的地图&#xff1a; fig.1 测试地图 opencv fig.2 opencv 显示的覆盖路径 rviz fi…

6.定时器分时复用测量占空比

1.CUBEMAX配置 测量PA6&#xff0c;PA7输出的占空比&#xff0c;只需要把主要的配置&#xff0c;配置为A6口就行&#xff0c;A7口黄色表示配置不正确&#xff0c;不用管。 2.软件代码 TIME.c中找到TIM3的初始化&#xff0c;在后面初始化A7口 void MX_TIM3_Init_PA7(void) {/*…

创新实训2024.05.25日志:Web应用技术选型

我们的web应用使用python web的fastapi框架&#xff0c;通过uvicorn开启web服务。 1. refs 官网文档&#xff1a;FastAPI (tiangolo.com) github&#xff1a;https://github.com/tiangolo/fastapi 2. 环境配置 python:3.11 uvicorn:0.29.0 pip install "uvicorn[stan…

老外卖27刀每月的教程已经更新

用了两天半的时间&#xff0c;边学习&#xff0c;边整理了一份老外的视频教程&#xff0c;涉及Facebook&#xff0c;YouTube&#xff0c;tiktok等大的流量平台&#xff0c;有案例&#xff0c;有分析&#xff0c;有如何做。 这个教程是老外讲的&#xff0c;没有什么玄乎的塑造价…

聊聊ChatGPT的本质

这是鼎叔的第九十八篇原创文章。行业大牛和刚毕业的小白&#xff0c;都可以进来聊聊。 阶段性总结下我对ChatGPT的基础理解&#xff0c;算是一篇学习思考笔记吧。其中难免有很多不准确的&#xff0c;或过于简略的地方&#xff0c;将来再迭代学习。 OpenAI做ChatGPT的底层逻辑…

如何利用线程池实现互联网验证码保护服务

如何利用线程池实现互联网验证码保护服务 1、业务背景与实现思路2、代码实操1、业务背景与实现思路 首先介绍一下业务背景,假设我们的系统是一个短视频播放网站,每个新加入的用户都需要注册账号并绑定手机号。为了验证用户手机的正确性,我们的系统会发送一条验证码到用户注…

K8s的kubectl的基本操作

K8s的kubectl的基本操作 K8s基本信息的查看 查看版本信息 kubectl versio查看资源对象简写 kubectl api-resources查看集群信息 kubectl cluster-info配置kubectl自动补全 source <(kubectl completion bash)查看master节点状态 kubectl get cs查看命名空间 kubectl…

【找出第 K 大的异或坐标值】python

4层循环暴力超时 class Solution:def kthLargestValue(self, matrix: List[List[int]], k: int) -> int:nums[]for a in range(len(matrix)):for b in range(len(matrix[0])):num0for i in range(a1):for j in range(b1):num^matrix[i][j]nums.append(num)nums.sort()retu…

类的内存对齐位段位图布隆过滤器哈希切割一致性哈希

文章目录 一、类的内存对齐1.1规则1.2原因 二、位段2.1介绍2.2内存分配问题2.3跨平台问题2.4使用的注意事项 三、位图的应用3.1 给40亿个不重复的无符号整数&#xff0c;找给定的一个数。&#xff08;int的范围可以到达42亿多&#xff09;3.2 给定100亿个整数&#xff0c;设计算…

Golang实现文件复制

方法&#xff1a;三种 package zdpgo_fileimport ("errors""io""os" )// CopyFile 使用io.Copy进行文件的复制&#xff0c;同时也会复制文件的所有权限 // param src 复制文件 // param des 目标文件 // return error 错误信息 func CopyFile(s…

2024年弘连网络FIC大会竞赛题线下决赛题

总结&#xff1a; FIC决赛的时候&#xff0c;很多小问题没发现&#xff0c;在pve平台做题确实很方便。 这套题目复盘完&#xff0c;服务器这块的知识确实收获了很多&#xff0c;对pve集群平台和网络拓扑也有了一定的认识&#xff0c;感谢各位大佬悉心指导。 接下来&#xff0…

【FPGA】Verilog:奇校验位生成器的实现(Odd Parity bit generator)

解释奇数奇偶校验位生成器和检查器的仿真结果及过程。 真值表和卡洛图: Odd Parity Bit Generator A B C

怎么在pyqt中显示matplotlib的绘图?

想要在pyqt中显示matplotlib的绘图&#xff0c;在绘图时&#xff0c;其实不必使用以下语句&#xff1a; matplotlib.use("Qt5Agg") # 声明使用QT5最关键的语句是&#xff1a; from matplotlib.backends.backend_qt5agg import FigureCanvasQTAggFigureCanvasQTAgg…

学 Python 具体能干什么?

Python 是一种功能强大、用途广泛的编程语言&#xff0c;因其简洁易读的语法和丰富的库生态系统而备受欢迎。学习 Python后&#xff0c;你可以从事以下几方面的工作&#xff1a; 1. Web 开发 Python 有很多流行的 Web 框架&#xff0c;如&#xff1a; Django&#xff1a;一个…

Android studio的Gradle出问题

Gradle sync failed: Plugin [id: com.android.application, version: 7.1.1, apply: false] was not found in any of the following sources: 在src里面的build.gradle中 plugins { id ‘com.android.application’ } 的上面加上 buildscript {repositories {jcenter()}depen…

从 0 开始实现一个网页聊天室 (小型项目)

实现功能 用户注册和登录好友列表展示会话列表展示: 显示当前正在进行哪些会话 (单聊 / 群聊) , 选中好友列表中的某个好友, 会生成对应的会话实时通信, A给B发送消息, B的聊天界面 / 会话界面能立刻显示新的消息 TODO: 添加好友功能用户头像显示传输图片 / 表情包历史消息搜…

禅道密码正确但是登录异常处理

禅道密码正确&#xff0c;但是登录提示密码错误的异常处理 排查内容 # 1、服务器异常&#xff0c;存储空间、数据库异常 # 2、服务异常&#xff0c;文件丢失等异常问题定位 # 1、df -h 排查服务器存储空间 # 2、根据my.php排查数据库连接是否正常 # 3、修改my.pho,debugtrue…