C++ 模拟实现mapset

目录

一、改造红黑树

1、模板T改造节点

2、提取节点中的key 

3、迭代器类

operator++ 

operator--

4、改造insert

5、红黑树迭代器 

6、 普通迭代器构造const迭代器

二、set

三、map


在stl中map和set的结构中,他们都使用一个红黑树进行封装。

由上图可知,set传给红黑树节点的两个模板参数都是key,而map传给的红黑树的第一个模板参数是key、第二个参数是pair,因此我们首先对上一篇文章中的红黑树进行改造。

一、改造红黑树

#pragma onceenum 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;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;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){if (_node->_right){// 1、右不为空,下一个就是右子树的最左节点Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{// 2、右为空,沿着到根的路径,找孩子是父亲左的那个祖先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){// 1、左不为空,找左子树最右节点Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 2、左为空,孩子是父亲的右的那个祖先Node* cur = _node;Node* parent = cur->_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:~RBTree(){_Destroy(_root);_root = nullptr;}
public:typedef __RBTreeIterator<T, T&, T*> itertaor;typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;itertaor begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return itertaor(cur);}itertaor end(){return itertaor(nullptr);}const_itertaor begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_itertaor(cur);}const_itertaor end() const{return const_itertaor(nullptr);}Node* Find(const K& key){Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}pair<itertaor, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(itertaor(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(itertaor(cur), false);}}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;if (grandfather->_left == parent){Node* uncle = grandfather->_right;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//     g//   p   u// c if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p   u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}else // (grandfather->_right == parent){//    g//  u   p//        cNode* uncle = grandfather->_left;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//    g//  u   p//        cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{//    g//  u   p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(itertaor(newnode), true);;}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 == NULL)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 RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}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;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}private:Node* _root = nullptr;
};

1、模板T改造节点

我们需要将节点结构体RBTreeNode中的数据成员_data的类型改为模板参数T。这样,每个节点就可以存储不同类型的数据,以适应map的节点存储pair结构,set的节点存储key。

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){}
};

2、提取节点中的key 

在上述代码中,我们可以看到`RBTree`类的定义如下:

template<class K, class T, class KeyOfT>
class RBTree
{// ...
};

其中,`K`是键的类型,`T`是值的类型,`KeyOfT`是一个辅助类也叫作仿函数,用于从值中提取键。在`set`中,键和值的类型相同,因此`KeyOfT`可以直接使用`K`。而在`map`中,键和值的类型不同,因此我们需要对`KeyOfT`进行修改。

对于`map`类,我们需要将`KeyOfT`修改为适应`pair<const K, V>`类型的辅助类。我们可以定义一个新的辅助类(仿函数)`MapKeyOfT`,其内部使用重载了函数调用运算符 operator(),这个辅助类的作用是从`pair<const K, V>`类型的值中提取出键`K`。同时,我们将`RBTree`的第三个模板参数修改为`MapKeyOfT`。

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

对于`set`类,我们需要将`KeyOfT`修改为配合map使用的辅助类。我们可以定义一个仿函数`SetKeyOfT`, 通过重载operator()直接返回key。同时,我们将第三个模板参数修改为’SetKeyOfT'。

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

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;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){}Self& operator--(){}
};

 这段代码定义了一个模板类__RBTreeIterator,它有三个模板参数:TRefPtr

  • T:表示节点中存储的数据类型。
  • Ref:表示解引用操作符*返回的引用类型。
  • Ptr:表示箭头操作符->返回的指针类型。

这个迭代器类用于遍历红黑树,并提供了对节点数据的访问功能。

在这个迭代器类中,有一个成员变量Node* _node,它指向红黑树中的一个节点。

  1. 构造函数__RBTreeIterator(Node* node)用于初始化迭代器对象,将_node指针指向传入的节点。
  2. 拷贝构造函数__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it)用于从普通迭代器构造const迭代器。它将传入迭代器的_node指针赋值给当前迭代器的_node成员变量。
  3. 重载解引用操作符*,使得可以通过迭代器访问节点的数据。它返回的是Ref类型的引用,可以直接修改节点的数据。
  4. 重载箭头操作符->,使得可以通过迭代器访问节点的数据。它返回的是Ptr类型的指针,可以通过指针访问节点的数据成员。
  5. 重载不等于操作符!=,用于比较两个迭代器是否不相等。它比较两个迭代器的_node指针是否相等。
  6. 重载前缀递增操作符++,使得可以将迭代器指向下一个节点。如果当前节点有右子节点,则下一个节点是右子树的最左节点;否则,沿着到根的路径,找到第一个孩子是父节点左孩子的祖先节点。
  7. 重载前缀递减操作符--,使得可以将迭代器指向上一个节点。如果当前节点有左子节点,则上一个节点是左子树的最右节点;否则,沿着到根的路径,找到第一个孩子是父节点右孩子的祖先节点。

operator++ 

    Self& operator++(){if (_node->_right){// 1、右不为空,下一个就是右子树的最左节点Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{// 2、右为空,沿着到根的路径,找孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}

首先,函数检查当前节点的右子节点是否存在。如果存在,说明存在右子树,下一个节点就是右子树中的最左节点。因此,函数将迭代器指向右子树中的最左节点,并结束函数的执行。

如果当前节点的右子节点为空,说明不存在右子树。在这种情况下,函数需要沿着到根的路径向上查找,直到找到一个节点,该节点是其父节点的左子节点。这是因为在红黑树中,右子树为空的节点的下一个节点是其父节点。

函数使用两个指针变量 cur 和 parent 来进行查找。

  • 开始时,将 cur 初始化为当前节点,将 parent 初始化为 cur 的父节点。
  • 然后,函数进入一个循环,只要 parent 存在且 cur 是 parent 的右子节点,就继续向上查找。
  • 在循环中,将 cur 更新为 parent,将 parent 更新为 cur 的父节点。
  • 这样,函数就可以找到下一个节点,并将迭代器指向该节点。

最后,函数返回迭代器自身的引用,以支持链式操作。

operator--

	Self& operator--(){if (_node->_left){// 1、左不为空,找左子树最右节点Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 2、左为空,孩子是父亲的右的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}

 首先,函数检查当前节点的左子节点是否存在。如果存在,说明存在左子树,前一个节点就是左子树中的最右节点。因此,函数将迭代器指向左子树中的最右节点,并结束函数的执行。

如果当前节点的左子节点为空,说明不存在左子树。在这种情况下,函数需要沿着到根的路径向上查找,直到找到一个节点,该节点是其父节点的右子节点。这是因为在红黑树中,左子树为空的节点的前一个节点是其父节点。

函数使用两个指针变量 cur 和 parent 来进行查找。

  • 开始时,将 cur 初始化为当前节点,将 parent 初始化为 cur 的父节点。
  • 然后,函数进入一个循环,只要 parent 存在且 cur 是 parent 的左子节点,就继续向上查找。
  • 在循环中,将 cur 更新为 parent,将 parent 更新为 cur 的父节点。
  • 这样,函数就可以找到前一个节点,并将迭代器指向该节点。

最后,函数返回迭代器自身的引用,以支持链式操作。

4、改造insert

1. 修改函数的参数类型为 `const T& data`,以接受要插入的数据。

返回类型 pair<iterator, bool> 是为了提供更多的信息给调用者,以便在插入操作后进行进一步的处理。

pair<iterator, bool> Insert(const T& data)
  • iterator:表示插入节点的迭代器。通过返回迭代器,调用者可以方便地访问插入的节点,进行后续的操作,如修改节点的值、删除节点等。

  • bool:表示插入是否成功的标志。通过返回布尔值,调用者可以知道插入操作是否成功。如果插入成功,布尔值为 true;如果插入失败(节点已存在),布尔值为 false

 

2. 将 `pair<K, V>` 替换为 `T`,因为现在的节点类型是 `RBTreeNode<T>`。

cur = new Node(data);

3. 在函数内部,将 `_kv` 替换为 `_data`,以匹配节点的数据成员名称。

if (cur->_data < data){// ...
}
else if (cur->_data > data){// ...
}
else{//查找失败return make_pair(iterator(cur), false);
}

4.  新增的 KeyOfT kot; 是为了创建 KeyOfT 类型的对象 kot,用于从节点数据类型 T 中提取键值。

KeyOfT kot;
while (cur)
{if (kot(cur->_data) < kot(data)){//..}else if (kot(cur->_data) > kot(data)){//..}else{//..}
}

5. 在修改后的 Insert 函数中,新增的 Node* newnode = cur; 是为了在返回 pair<iterator, bool> 时,将插入的节点的迭代器保存下来。

Node* newnode = cur;
  • 在原始的代码中,返回的迭代器是通过 iterator(cur) 创建的,其中 cur 是指向新插入节点的指针。然而,在后续的操作中,cur 的值可能会发生变化,因为在红黑树的调整过程中,节点的位置可能会发生改变。
  • 为了确保返回的迭代器指向插入的节点,我们将 cur 的值保存在 newnode 中。这样,在返回 pair<iterator, bool> 时,我们使用 iterator(newnode) 创建迭代器,确保迭代器指向插入的节点。
  • 因此,Node* newnode = cur; 的目的是为了保存插入节点的指针,以便在返回迭代器时使用。这样可以确保返回的迭代器指向正确的节点,而不受后续操作的影响。

6. 在函数的返回语句中,使用 `make_pair` 创建一个 `pair` 对象,其中包含插入的迭代器和插入是否成功的标志。

return make_pair(iterator(newnode), true);

5、红黑树迭代器 

template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T, T&, T*> itertaor;typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;itertaor begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return itertaor(cur);}itertaor end(){return itertaor(nullptr);}const_itertaor begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_itertaor(cur);}const_itertaor end() const{return const_itertaor(nullptr);}
  • 首先,我们定义了两个迭代器类型 iterator 和 const_iterator,分别对应可修改和只读的迭代器。这些迭代器是通过 __RBTreeIterator 结构体模板实例化得到的。
  • 然后,我们定义了 begin() 和 end() 函数,分别用于返回红黑树的起始迭代器和结束迭代器。
  • 在 begin() 函数中,我们从根节点开始,沿着左子节点的路径向下遍历,直到找到最左边的节点。这个节点就是红黑树中最小的节点,也是起始节点。我们将其作为参数传递给 __RBTreeIterator 构造函数,创建一个迭代器对象,并将其返回。
  • 在 end() 函数中,我们直接返回一个迭代器对象,其指针成员 _node 设置为 nullptr,表示结束位置。
  • 对于 const 重载的版本,逻辑与非 const 版本相同,只是返回的是 const_iterator 类型的迭代器对象。

6、 普通迭代器构造const迭代器

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){}
}
  • const __RBTreeIterator<T, T&, T*>& it 部分没有显式的强制类型转换。它是一个常量引用,用于接收普通迭代器对象的引用作为参数,如果需要将普通迭代器对象转换为const迭代器对象,编译器会自动进行隐式类型转换。
  • 因为参数加上了const修饰符,这意味着它是一个常量引用。当使用普通迭代器对象调用构造函数时,编译器会进行隐式类型转换,将普通迭代器对象转换为const迭代器对象。

二、set

#pragma once#include "RBTree.h"namespace MySet
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::const_itertaor iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_itertaor const_iterator;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;};
}
  • 在命名空间MySet中,定义了一个内部结构体SetKeyOfT,它是一个仿函数,用于从键中提取键值。在这个仿函数中,重载了函数调用操作符operator(),接受一个键key作为参数,并返回该键本身。
  • 接下来是set类的定义。它使用了模板参数K,表示集合中元素的类型。在set类中,使用了RBTree类来实现红黑树的功能。RBTree的模板参数为K、K和SetKeyOfT,表示键的类型、值的类型和键提取仿函数的类型。
  • set类中定义了两个迭代器类型:iterator和const_iterator,它们分别是RBTree<K, K, SetKeyOfT>::const_iterator的别名。这两个迭代器类型用于遍历集合中的元素。
  • set类还提供了begin和end方法,用于返回集合的起始迭代器和结束迭代器。begin方法返回的是红黑树的起始迭代器,而end方法返回的是红黑树的结束迭代器。
  • 此外,set类还提供了insert方法,用于向集合中插入元素。它接受一个键key作为参数,并返回一个pair对象,其中包含一个迭代器和一个布尔值。迭代器指向插入的元素,布尔值表示插入是否成功。
  • 最后,set类中有一个私有成员变量_t,它是一个RBTree<K, K, SetKeyOfT>类型的对象,用于存储集合的元素。

三、map

#pragma once#include "RBTree.h"namespace MyMap
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::itertaor iterator;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;};
  • 命名空间MyMap中,定义了一个内部结构体MapKeyOfT,它是一个仿函数,用于从键值对中提取键。在这个仿函数中,重载了函数调用操作符operator(),接受一个键值对kv作为参数,并返回该键值对中的键first
  • 接下来是map类的定义。它使用了两个模板参数KV,分别表示键和值的类型。在map类中,使用了RBTree类来实现红黑树的功能。RBTree的模板参数为Kpair<const K, V>MapKeyOfT,表示键的类型、键值对的类型和键提取仿函数的类型。
  • map类中定义了一个迭代器类型iterator,它是RBTree<K, pair<const K, V>, MapKeyOfT>::iterator的别名。这个迭代器类型用于遍历映射中的键值对。
  • map类提供了beginend方法,用于返回映射的起始迭代器和结束迭代器。begin方法返回的是红黑树的起始迭代器,而end方法返回的是红黑树的结束迭代器。
  • 此外,map类还重载了下标操作符[],使得可以通过键访问对应的值。它接受一个键key作为参数,并返回对应的值的引用。如果键不存在于映射中,则会自动插入一个新的键值对,并返回对应值的引用。
  • map类还提供了insert方法,用于向映射中插入键值对。它接受一个键值对kv作为参数,并返回一个pair对象,其中包含一个迭代器和一个布尔值。迭代器指向插入的键值对,布尔值表示插入是否成功。
  • 最后,map类中有一个私有成员变量_t,它是一个RBTree<K, pair<const K, V>, MapKeyOfT>类型的对象,用于存储映射的键值对。

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

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

相关文章

Java 枚举和注解

一、枚举类 把具体的对象一个一个例举出来的类就称为枚举类 枚举对应英文(enumeration, 简写 enum)枚举是一组常量的集合。可以这里理解&#xff1a;枚举属于一种特殊的类&#xff0c;里面只包含一组有限的特定的对象。 1.实现方式1——自定义类实现枚举 public class Enume…

Oracal学习

Oracle是什么 是甲骨文公司的一款支持事务且吞吐量高的数据库特点&#xff1a; &#xff08;1&#xff09;支持多用户、大事务量的事务处理 &#xff08;2&#xff09;数据安全性和完整性控制 &#xff08;3&#xff09;支持分布式数据处理 &#xff08;4&#xff09;可移植性…

【C++】C++入门基础讲解(一)

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 导读 经过一段时间的C语言学习&#xff0c;我们以及基本掌握了C语言的知识&#xff0c;今天&#xff0c;我们就开始学习C&#xff0c;…

2024最新幻兽帕鲁服务器多少钱一个?

幻兽帕鲁服务器多少钱&#xff1f;价格便宜&#xff0c;阿里云4核16G幻兽帕鲁专属服务器32元1个月、66元3个月&#xff0c;4核32G配置113元1个月、339元3个月&#xff1b;腾讯云4核16G14M服务器66元1个月、277元3个月、1584元一年。阿腾云atengyun.com分享阿里云和腾讯云palwor…

SpringBoot的自动装配原理

一、SpringBootConfiguration注解的作用 SpringBootApplication注解是SpringBoot项目的核心注解,加在启动引导类上。点击进去可以发现SpringBootApplication注解是一个组合注解。其中SpringBootConfiguration和EnableAutoConfiguration是由Spring提供的,剩下的注解是由JDK提供的…

简单计算与模拟1:鸡兔同笼(POJ 3237)

1 问题描述 图1 问题描述 2 解题思路 鸡有两只脚&#xff0c;兔子有四只脚&#xff0c;且输入数据可能为奇数&#xff0c;使用公式计算即可。 3 设计代码 #include <cstdio> int main() {int nCases, nFeets;while (scanf("%d", &nCases) ! EOF){for (in…

负载均衡-Ribbon-自定义负载均衡算法

1.Ribbon 是什么 SpringCloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具主要功能是提供客户端的软件负载均衡算法&#xff0c;将 Netflix 的中间服务处连接在一起Ribbon的客户端组件提供一系列完整的配置项&#xff0c;如&#xff1a;连接超时、重试等。简…

echarts 绘制垂直滚动热力图

问题1&#xff1a;提示功能无效 问题2&#xff1a;值筛选无效 效果 在线浏览 下载echarts官网例子(heatmap Examples - Apache ECharts) 稍作改动&#xff1a; generateData 入参改为长度和宽度noise.perlin2(i / 40, j / 20) Math.random() * 5y轴倒置指定zlevel为2 通过定…

蓝桥杯备战——7.DS18B20温度传感器

1.分析原理图 通过上图我们可以看到DS18B20通过单总线接到了单片机的P14上。 2.查阅DS18B20使用手册 比赛的时候是会提供DS18B20单总线通讯协议的代码&#xff0c;但是没有提供读取温度数据的代码&#xff0c;所以还是需要我们去查看手册&#xff0c;我只把重要部分截下来了 …

python-自动篇-运维-根据计算机硬盘、主板、CPU生成注册信息

文章目录 准备代码效果 准备 本实例需要使用WMI模块&#xff0c;所以需要安装WMI模块。在安装WMI模块之前&#xff0c;先要安装pywin32模块&#xff0c;WMI模块需要win32api的支持。使用pip安装pywin32模块和WMI模块的代码如下&#xff1a; pip install win32com pip install …

Ubuntu 22.04 安装tomcat

tomcat是常用的Java服务容器,这篇文章我们就来讲讲如何安装它。 更新软件包 首先是更新软件包,这是最常规的操作 sudo apt update 然后是开始安装,不多一会就可以安装好了 sudo apt install tomcat9 然后看一下状态 sudo systemctl status tomcat9 发现虽然启动了,但…

Python第十一章(推导式)

一。列表推导式&#xff1a;目的化简代码&#xff0c;可以改变数据 例句1&#xff1a;如果while循环继续创建序列累加 例句2&#xff1a;如果for循环进行列表累加 1.循环列表推导式进行累加&#xff1a; 注释&#xff1a;第一个i表示的是返回的数据于列表&#xff0c;for后面内…

windows上使用anconda安装tensorrt环境

windows上使用anconda安装tensorrt环境 1 安装tensorrt1.1 下载最新的稳定的tensorrt 8.6.1(tensorrt对应的cuda、cudnn等版本是参考链接4)1.2 将tensorrt添加到环境变量1.3 安装tensorrt依赖1.4 安装Pycuda1.5 安装pytorch 2 测试2.1 测试TensorRT 样例(这个测试主要来源于参考…

【Python】01快速上手爬虫案例一:搞定豆瓣读书

文章目录 前言一、VSCodePython环境搭建二、爬虫案例一1、爬取第一页数据2、爬取所有页数据3、格式化html数据4、导出excel文件 前言 实战是最好的老师&#xff0c;直接案例操作&#xff0c;快速上手。 案例一&#xff0c;爬取数据&#xff0c;最终效果图&#xff1a; 一、VS…

docker生命周期管理命令

文章目录 前言1、docker create2、docker run2.1、常用选项2.2、系统2.3、网络2.4、健康检查 3、docker start/stop/restart4、docker kill5、docker rm6、docker pause/unpause总结 前言 在云原生时代&#xff0c;Docker已成为必不可少的容器管理工具。通过掌握Docker常用的容…

uniCloud发行部署H5进行网页托管

生成文件&#xff0c;生成文件这个和我们平时用uniapp 生成H5的时候是一样的&#xff0c;我们可以选择hash 或者history 模式&#xff0c;默认的这是显示的根目录&#xff0c;如果我们在根目录下建立了H5目录&#xff0c;那么我们在发布H5的时候&#xff0c;是需要在manifest.j…

数据恢复与硬盘修理

目录 第1章 基础知识 1.1 数据恢复技术的发展和研究现状 1.2 数据恢复技术的层次与体系 1&#xff0e;网络层 2&#xff0e;网络存储层 DAS NAS 3&#xff0e;磁盘阵列层 4&#xff0e;磁盘层 5&#xff0e;文件系统层 6&#xff0e;文件层 7&#xff0e;覆盖恢复…

越活越“老“越值钱——来喝了这碗孟婆汤吧

目录 一、背景介绍二、思路&方案三、过程1.开头先从小编的行业说起2.扩展到各行各业&#xff0c;从小编的视角和认知的对比3.纵观人类发展和科技发展的对比4.我身边就有越活越"老"越值钱的人5.如何做到这一点的基本逻辑 四、总结 一、背景介绍 对于自己的未来&a…

Java强训day4(选择题编程题)

选择题 接口中的方法是为了让重写编程题 题目 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int a_b sc.nextInt();int b_c sc.nextInt();int ab sc.nextInt();int bc sc.nextInt();for(in…

mysql索引失效

索引失效 1.违反最左前缀法则 查询要从索引的最左前列开始&#xff0c;不能跳过索引中的列 没有违反 违反 部分违反&#xff0c;只有最左边的命中索引 2.查询右边范围的列&#xff0c;不能使用索引 范维列不满足最左前缀 3.索引列上进行运算&#xff0c;索引会失效 改变…