C++:map和set的封装

      关于红黑树的模拟实现,大家不清楚的先去看看博主的博客再来看这篇文章,因为set和map的封装底层都是利用用的红黑树。所以这里不会过多介绍红黑树的相关内容,而更多的是去为了契合STL中的红黑树去进行改造,让封装的set和map能够去复用我们的这份代码

DS进阶:AVL树和红黑树-CSDN博客

      在模拟实现之前,我们肯定要尝试去看看源码是如何实现的!我们会发现其实map和set的底层都是用的红黑树去封装的

      但是你可能会有这样的疑惑,map是kv模型,set是k模型,那难道stl底层封装了两颗红黑树么??其实并不是的,创建stl的大佬们为了增加代码的复用性,想方设法地想让map和set同时复用一颗红黑树。而解决方法就是通过控制模版参数来区分map和set。

     既然底层是套的红黑树的壳子,我们就要来研究库里面的红黑树究竟通过了什么方法来让map和set都能够复用这份代码。

一、STL中的红黑树

1.1 利用模版参数控制和区分map和set

我们先来看看stl中的红黑树的模版参数,然后进行分析

   接下来我们来看看第三个模版参数的作用究竟是什么

总结:

第1个模版参数是为了帮助我们拿到Key的类型,因为find、erase的接口都是Key类型比较方便

第2个模版参数决定了红黑树节点中存的是key还是pair,以此来区分map和set

第3个模版参数是通过仿函数决定了是拿什么去进行比较,对set来说就是拿key,对pair来说就是拿他的first。

第4个模版参数是具体的比较逻辑,比如说我们传的是指针,但是我们并不想通过指针比而是通过指针解引用的类型比,就可以通过传这个仿函数去控制其比较的行为。

第5个是stl实现的一个堆内存管理器,是为了提高从堆区申请内存的效率,基本上所有的stl容器都会涉及到这个,所以目前暂时不需要太在意!

1.2 stl中的红黑树结构

在该图中,设置了一个哨兵节点,哨兵节点的左指向最小节点5,最大节点的右指向哨兵节点header, 为什么要这样设计呢??

      STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,
可以得到一个有序的序列,
因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位
置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?
能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行--操作,必须要能找最
后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:

 

       但是这样虽然方便我们找到第一个节点和最后一个节点,但是每一次都要最最左端和最右端的节点进行和头节点之间的联系,其实比较麻烦,所以下面我们直接改造成不带哨兵节点的红黑树。去模拟实现迭代器。

1.3 改造并模拟实现红黑树的迭代器

但是最最关键的逻辑就是,实现++和--这样迭代器才能跑的起来,下面我们来进行分析

迭代器的封装

template<class T,class Ref,class Ptr>
struct _RBTreeIterator
{typedef RBTreeNode<T> Node;typedef _RBTreeIterator<T, Ref, Ptr> Self; //返回一个自身的迭代器Node* _node;_RBTreeIterator(Node* node)  //利用节点去构造迭代器:_node(node){}// 1、typedef __RBTreeIterator<T, T&, T*> itertaor;  拷贝构造// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;//  支持普通迭代器构造const迭代器的构造函数_RBTreeIterator(const _RBTreeIterator<T, T&, T*>& it) //隐私类型转化:_node(it._node){}Ref operator*(){return _node->_data; //解引用拿到对应的东西  map拿到pair set拿到key}Ptr operator->() //返回对应的指针类型{return &operator*();}bool operator!=(const Self& s){return _node != s._node;//判断两个迭代器是否相同}bool operator==(const Self& s){return _node == s._node;//判断两个迭代器是否相同}Self& operator++()  //实现迭代器的++{if (_node->_right){//如有右不为空,那么就去找到  右子树的最左路节点Node* subright = _node->_right;while (subright->_left)  subright = subright->_left; //找到最左路节点_node = subright;}else{//右为空,沿着到根的路径,找孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}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 = parent->_parent;}_node = parent;}return *this;}
};

1.4 红黑树实现的全部代码 

enum Colour
{RED,BLACK,
};template<class T> //T表示传的是K还是pair
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;typedef _RBTreeIterator<T, Ref, Ptr> Self; //返回一个自身的迭代器Node* _node;_RBTreeIterator(Node* node)  //利用节点去构造迭代器:_node(node){}// 1、typedef __RBTreeIterator<T, T&, T*> itertaor;  拷贝构造// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;//  支持普通迭代器构造const迭代器的构造函数_RBTreeIterator(const _RBTreeIterator<T, T&, T*>& it) //隐私类型转化:_node(it._node){}Ref operator*(){return _node->_data; //解引用拿到对应的东西  map拿到pair set拿到key}Ptr operator->() //返回对应的指针类型{return &operator*();}bool operator!=(const Self& s){return _node != s._node;//判断两个迭代器是否相同}bool operator==(const Self& s){return _node == s._node;//判断两个迭代器是否相同}Self& operator++()  //实现迭代器的++{if (_node->_right){//如有右不为空,那么就去找到  右子树的最左路节点Node* subright = _node->_right;while (subright->_left)  subright = subright->_left; //找到最左路节点_node = subright;}else{//右为空,沿着到根的路径,找孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}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 = parent->_parent;}_node = parent;}return *this;}
};//K是为了单独拿到key的类型 因为find erase 的接口都是key   而第二个模版参数T决定是这边传的是pair还是key
template<class K, class T,class KeyOfT>  //KeyofT 取出来比较的是k 还是pair中的k
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef _RBTreeIterator<T, T&, T*>  iterator;typedef _RBTreeIterator<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 const_iterator(cur);}const_iterator end() const{return const_iterator(nullptr);}~RBTree(){_Destroy(_root);_root = nullptr;}Node* Find(const K& key){Node* cur = _root;KeyOfT kot;//控制  是在pair中拿key还是直接拿keywhile (cur){if (kot(cur->_data) < key) cur = cur->_right;  //我比你小,你往右找else if (kot(cur->_data) > key)  cur = cur->_left;  //我比你大,你往左找else return cur;}return nullptr;//说明找不到}//先用搜索树的逻辑插入节点,然后再去更新平衡因子。pair<iterator,bool> Insert(const T& data){//如果为空树,新节点就是根if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root),true);}KeyOfT kot;//控制  是在pair中拿key还是直接拿key//如果不为空树Node* parent = nullptr;Node* cur = _root;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);//相等 }//此时肯定是对应地接在parent的后面cur = new Node(data);Node* newnode = cur;//记住新加入的节点if (kot(parent->_data)> kot(data))   parent->_left = cur;                //比父亲小连左边else  parent->_right = cur; //比父亲大连右边//别忘了父亲指针cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//情况1,如果u为存在且为红if (grandfather->_left == parent)//如果p是g的左边,u就在右边{Node* uncle = grandfather->_right;//情况1,如果u为存在且为红 p u变黑,g变红 向上调整if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else //情况2或者情况3, u为黑或者不存在   旋转+变色{if (cur == parent->_left) //情况2 右单旋+p变黑 g变红{//      g//   p    u// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else  //情况3 右左双旋  c变黑 g变红{//          g//   p     u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//情况2和情况3都要跳出循环}}else//if (grandfather->_right == parent)//如果p是g的右边,u就在左边    几乎一样,就是旋转的逻辑不同{Node* uncle = grandfather->_left;//情况1,如果u为存在且为红 p u变黑,g变红 向上调整if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else//情况2或者情况3, u为黑或者不存在   旋转+变色{if (cur == parent->_right) //情况2 左单旋+p变黑 g变红{//      g//   p    u//          cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else  //情况3 左右双旋  c变黑 g变红{//          g//   p     u//       cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//情况2和情况3都要跳出循环}}}_root->_col = BLACK; //预防情况1出现 parent就是根的情况 此时无论如何_root变成黑,总没错return make_pair(iterator(newnode), true);}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){if (_root && _root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}int benchmark = 0;//找到一条路径作为基准值 然后看看其他路径是否相等Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}// 连续红色节点return _Check(_root, 0, benchmark);}int Height(){return _Height(_root);}private:void _Destroy(Node* root){if (root == nullptr) return;//后序遍历销毁_Destroy(root->_left);_Destroy(root->_right);delete root;}int _Height(Node* root){if (root == nullptr)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _Check(Node* root, int blackNum, int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}//旋转代码和AVL树是一样的,只不过不需要搞平衡因子void RotateL(Node* parent){//旋转前,先记录对应的节点Node* subR = parent->_right;Node* subRL = subR->_left;Node* ppnode = parent->_parent;//子树的前驱节点//先让b变成30的边parent->_right = subRL;if (subRL) subRL->_parent = parent;//让30变成60的左边subR->_left = parent;parent->_parent = subR;//此时与前驱节点连接起来 如果前驱节点为空,直接改变根if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边else{if (ppnode->_left == parent) ppnode->_left = subR;else ppnode->_right = subR;//向上连接subR->_parent = ppnode;}}void RotateR(Node* parent){//旋转前,先记录对应的节点Node* subL = parent->_left;Node* subLR = subL->_right;Node* ppnode = parent->_parent;//子树的前驱节点//先让b变成60的左边parent->_left = subLR;if (subLR) subLR->_parent = parent;//让60变成30的右边subL->_right = parent;parent->_parent = subL;//此时与前驱节点连接起来 如果前驱节点为空,直接改变根if (ppnode == nullptr){_root = subL;_root->_parent = nullptr;}//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边else{if (ppnode->_left == parent) ppnode->_left = subL;else ppnode->_right = subL;//向上连接subL->_parent = ppnode;}}Node* _root = nullptr;
};

二、set的模拟实现

前面我们已经将架子搭好了,这个时候就可以直接开始用了!!

namespace cyx
{template<class K>class set{struct SetKeyofT{ const K& operator()(const K& key) //为了跟map保持一致{return key;}};public:typedef typename RBTree< K,K,SetKeyofT>::iterator  iterator;//在没有实例化的时候  编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题iterator begin(){return _t.begin();}iterator end(){return _t.end();}pair<iterator, bool> insert(const K&key){return _t.Insert(key);}private:RBTree<K, K, SetKeyofT> _t;};

注意:

1、在没有实例化的时候 ,编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题

 2、对于insert返回值的改造,本质上是为了map去服务的,set只是配合而已。

三、map的模拟实现

3.1 insert的改装

在stl中 insert的返回值是pair<iterator,bool> 一开始我不太能理解为什么要这么设计。后来我明白了其实本质上为了后面重载[ ]的实现做铺垫。我们可以通过返回值去拿到iterator,并对对应节点的value进行直接修改!!

//先用搜索树的逻辑插入节点,然后再去更新平衡因子。
pair<iterator,bool> Insert(const T& data)
{//如果为空树,新节点就是根if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root),true);}KeyOfT kot;//控制  是在pair中拿key还是直接拿key//如果不为空树Node* parent = nullptr;Node* cur = _root;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);//相等 }//此时肯定是对应地接在parent的后面cur = new Node(data);Node* newnode = cur;//记住新加入的节点if (kot(parent->_data)> kot(data))   parent->_left = cur;                //比父亲小连左边else  parent->_right = cur; //比父亲大连右边//别忘了父亲指针cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//情况1,如果u为存在且为红if (grandfather->_left == parent)//如果p是g的左边,u就在右边{Node* uncle = grandfather->_right;//情况1,如果u为存在且为红 p u变黑,g变红 向上调整if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else //情况2或者情况3, u为黑或者不存在   旋转+变色{if (cur == parent->_left) //情况2 右单旋+p变黑 g变红{//      g//   p    u// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else  //情况3 右左双旋  c变黑 g变红{//          g//   p     u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//情况2和情况3都要跳出循环}}else//if (grandfather->_right == parent)//如果p是g的右边,u就在左边    几乎一样,就是旋转的逻辑不同{Node* uncle = grandfather->_left;//情况1,如果u为存在且为红 p u变黑,g变红 向上调整if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}else//情况2或者情况3, u为黑或者不存在   旋转+变色{if (cur == parent->_right) //情况2 左单旋+p变黑 g变红{//      g//   p    u//          cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else  //情况3 左右双旋  c变黑 g变红{//          g//   p     u//       cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//情况2和情况3都要跳出循环}}}_root->_col = BLACK; //预防情况1出现 parent就是根的情况 此时无论如何_root变成黑,总没错return make_pair(iterator(newnode), true);
}

3.2 重载[ ]的实现

V& operator[](const K& key)
{pair<iterator, bool> ret = _t.Insert(make_pair(key, V())); //默认构造return ret.first->second;
}

通过insert拿到对应位置的迭代器,然后指向其second 这样就可以直接进行修改了。 

3.3 模拟实现的代码

namespace cyx
{template<class K, class V>class map{struct MapKeyofT{const K& operator()(const pair<const K, V>& kv) //为了跟map保持一致{return kv.first;}};public://模版类型的内嵌类型  加typenametypedef typename RBTree<K, pair<const K, V>, MapKeyofT>::iterator  iterator;//在没有实例化的时候  编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题iterator begin(){return _t.begin();}iterator end(){return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V())); //默认构造return ret.first->second;}pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<const K,V>, MapKeyofT> _t;};

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

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

相关文章

CMake使用

一、CMake 是什么 CMake 是一个跨平台的自动化构建系统&#xff0c;它使用配置文件 CMakeLists.txt 来管理软件构建过程。CMake 基于 Makefile 做了二次开发。 二、单个文件目录 # CMake 最低版本号要求 cmake_minimum_required(VERSION 3.16.3)# 工程名 project(CMakeSingle)…

uniapp自定义返回事件(封装)

uniapp自定义返回事件 在我们使用uniapp时&#xff0c;我们导航栏一般都是自定义的&#xff0c;比如用uview框架的导航栏&#xff0c;那么返回事件通常会遇到以下几个问题 返回事件前需要做一些额外的处理 h5项目刷新页面后返回失效 返回按钮点击后到指定页面 如果只是监听返…

PhotosCollage for Mac:优雅且实用的照片拼贴软件

PhotosCollage for Mac是一款优雅且实用的照片拼贴软件&#xff0c;为Mac用户提供了一个便捷、高效的平台&#xff0c;以创建精美、个性化的照片拼贴作品。 PhotosCollage for Mac v1.4.1激活版下载 该软件界面简洁直观&#xff0c;操作便捷。用户只需将想要拼贴的照片拖入“照…

CSS基础:position定位的5个类型详解!

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合…

Andorid复习

组件 TextView 阴影 android:shadowColor"color/red" 阴影颜色android:shadowRadius"3.0" 阴影模糊度&#xff08;大小&#xff09;android:shadowDx"10.0" 横向偏移android:shadowDy"10.0" 跑马灯 这里用自定义控件 public cla…

日本极致产品力 | 源自内蒙古,日本99.7%的人都喝过都百年畅销饮料

​《极致产品力》日本深度研学是一个顾问式课程,可以帮助企业找产品、找方向、找方法,在日本终端市场考察中洞悉热销产品背后的成功逻辑,了解最新最前沿的产品趋势和机会。结合日本消费趋势中国转化的众多经验,从品牌、包装、卖点、技术和生产工艺等多方面寻找中国市场的解决方…

WIFI/BT中蓝牙的硬件资源是如何调度的 UART和PCM接口传输的是什么信号

安卓或IOS手机中&#xff0c;wifi/bt中的蓝牙是如何调度硬件资源的&#xff0c;尤其是UART和PCM是如何分配的。M.2 wifi/bt模块或其他形式的模块中&#xff0c;蓝牙是如何调度硬件资源的&#xff0c;尤其是UART和PCM是如何分配的。今天我们就图文并茂的解决这个问题。 蓝牙文件…

新买的设备自带的仪器校准证书,是否可以作为校准报告使用?

设备在刚刚购买时&#xff0c;有些商家会承诺&#xff0c;在交付设备的同时&#xff0c;还会交付设备的校准证书&#xff0c;这证书是附赠的&#xff0c;属于商家给客户的一种福利&#xff0c;面对附赠的仪器校准证书&#xff0c;很多客户也会有疑惑&#xff0c;这附赠的证书有…

UTONMOS:用区块链技术拓展商业边界在哪里?

引言 大约从 2021 年Web 3 这个新概念开始受到风险基金和科技圈的普遍关注。但如果你对过去几年区块链的发展历史足够了解&#xff0c;就应该已经意识到现在的 Web 3 并不是什么新技术&#xff0c;甚至不是旧技术的进步&#xff0c;它只是一个基于区块链技术的宏大构想。 我是…

Unity 如何制作和发布你的 Package

一、制作你的第一个 Package Unity Package 不做过多赘述&#xff0c;像 URP 本质上也是一个 Package&#xff0c;在 Unity 中可以通过菜单栏 → Window → Package manager 来管理你当前的所有 Package 本篇文章主要介绍&#xff1a;如何制作并发布属于你的 Package 1.1 Pac…

自制贪吃蛇小游戏

此片文章涉及到到控制台设置的相关操作&#xff0c;虚拟键码&#xff0c;宽字符输出等&#xff0c;有些地方大家可能会看不懂&#xff0c;可以阅读以下文章来进一步了解&#xff1a; 控制台程序设置-CSDN博客 效果展示&#xff1a; QQ2024428-181932 源码已放在文章结尾 目录 …

Graph Neural Networks(GNN)学习笔记

本学习笔记的组织结构是&#xff0c;先跟李沐老师学一下&#xff0c;再去kaggle上寻摸一下有没有类似的练习&#xff0c;浅做一下&#xff0c;作为一个了解。 ———————————0428更新—————————————— 课程和博客看到后面准备主要看两个&#xff1a;GCN和…

ubuntu安装Anaconda安装及conda使用

一. 安装anaconda3详细教程 1、下载镜像 清华大学开源软件镜像站下载地址&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 下拉到最低端选择Linux&#xff0c;选择最新版&#xff08;32/64位&#xff09;下载。这里我下载的是版本Anaconda3-4.3.30-Linux…

Java中的File类

File类概述和构造方法 File&#xff1a;它是文件和目录路径名的抽象表示 文件和目录是可以通过File封装成对象的 对于File而言&#xff0c;其封装的并不是一个真正存在的文件&#xff0c;仅仅是一个路径名而已&#xff0c;它可以存在&#xff0c;也可以不存在 我们对Fie的操…

大厂常见算法50题-替换空格

专栏持续更新50道算法题&#xff0c;都是大厂高频算法题&#xff0c;建议关注, 一起巧‘背’算法! 文章目录 题目解法一 String类replace方法解法二 遍历替换总结 题目 解法一 String类replace方法 String类自带的replace&#xff0c;方法传入两个char类型的参数&#xff0c;分…

【MySQL 数据宝典】【索引原理】- 004 优化示例-join in exist

一、join 优化原理 1.1 基本连接方式介绍 JOIN 是 MySQL 用来进行联表操作的&#xff0c;用来匹配两个表的数据&#xff0c;筛选并合并出符合我们要求的结果集。 1.2 驱动表的定义 1.2.1 什么是驱动表 多表关联查询时,第一个被处理的表就是驱动表,使用驱动表去关联其他表.驱…

使用 SSH 密钥配置 Git 账号需要以下步骤

1、生成 SSH 密钥&#xff1a; 如果你还没有 SSH 密钥&#xff0c;可以使用以下命令在电脑终端中生成一个新的 SSH 密钥&#xff1a; ssh-keygen -t rsa -b 4096 -f /Users/XXXX/.ssh/id_rsa_my_personal -C "your_emailexample.com" ssh-keygen 是用于生成 SSH 密…

《Fundamentals of Power Electronics》——Buck、Boost、Buck-Boost三个电路的CCM-DCM工作特性总结

Buck、Boost、Buck-Boost这三个电路的CCM-DCM工作特性总结如下表所示&#xff1a; Buck、Boost、Buck-Boost这三个电路工作在DCM模式下电压传输比的对比图如下所示&#xff1a; 由上图可知&#xff0c;Buck-Boost电路的工作特性是一条斜率为的直线&#xff0c;Buck电路和Boost电…

RK3588 - RKNN(Rockchip 神经处理单元)的逆向工程

本文翻译自https://jas-hacks.blogspot.com/2024/02/rk3588-reverse-engineering-rknn.html RK3588 NPU 的内部操作和功能主要隐藏在名为RKNPU2的闭源 SDK 中。由于对大型语言模型 (LLM) 的兴趣以及对transform模型最佳矩阵乘法的追求&#xff0c;想了解 RKNPU SDK 新引入的矩阵…

自动开箱机:提升包装物流效率的关键设备

随着电子商务的飞速发展&#xff0c;物流行业面临着重要的挑战和机遇。如何在保证服务质量的同时&#xff0c;提高物流效率&#xff0c;降低成本&#xff0c;成为摆在物流企业面前的重要课题。在这个背景下&#xff0c;自动开箱机以其高效、精准、省力的特点&#xff0c;正逐渐…