【数据结构】二叉搜索树


🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🐌 个人主页:蜗牛牛啊
🔥 系列专栏:🛹数据结构、🛴C++
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹


文章目录

  • 二叉搜索树的概念
  • 二叉搜索树的操作及实现
    • 二叉搜索树的结构
    • 二叉搜索树的构造函数
    • 二叉搜索树的接口(非递归实现)
      • 二叉搜索树的插入
      • 二叉搜索树的中序遍历
      • 二叉搜索树的查找
      • 二叉搜索树的删除
    • 二叉搜索树的接口(递归实现)
      • 二叉搜索树的插入
      • 二叉搜索树的查找
      • 二叉搜索树的删除
    • 二叉搜索树的拷贝构造
    • 二叉搜索树的赋值
    • 二叉搜索树的析构函数
  • 二叉搜索树的应用
  • 二叉搜索树性能分析

二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

  • 它的左右子树也分别为二叉搜索树

image-20230724112755992

二叉搜索树还有一个特征:按照中序走的话是一个升序的状态。所以二叉树搜索树可以叫做二叉排序树或二叉查找树。

二叉搜索树的操作及实现

二叉搜索树的结构

首先实现一个结点类,结点类当中包含三个成员变量:结点值、左指针、右指针,同时结点类当中要对成员变量进行初始化,需要实现一个构造函数,用于将结点的左右指针置空和初始化指定结点值。

结点类的代码实现:

//二叉树搜索树结点类
template<class K>
struct BSTreeNode {BSTreeNode<K>* _left;//左指针BSTreeNode<K>* _right;//右指针K _key;//节点值//构造函数BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}
};
template<class K>
class BSTree {//喜欢在类里进行类型重定义,因为受到类域的限制typedef BSTreeNode<K> Node;
private:Node* _root = nullptr; //可以给一个构造函数,也可以直接写一个缺省值
};

二叉搜索树的构造函数

//构造函数
BSTree():root(nullptr)
{}

我们也可以这样写:

//default:默认情况下不会生成,让其强制生成构造函数
BSTree() = default;//指定强制生成默认构造

二叉搜索树的接口(非递归实现)

二叉搜索树的插入

通过插入函数在二叉搜索树中插入一个值,如果成功返回true,失败返回false。

当我们想进行插入数据时,要和树的根结点及各个子树的根结点进行比较,如果待插入结点值比当前结点小就插入到该结点的左子树;如果待插入结点值比当前节点值大就插入到该结点的右子树。

默认的搜索二叉树是不允许冗余的,有相同的值会插入失败。

根的值是怎么来的?插入的第一个值就是根。所以如果是同样的值,插入的顺序不同二叉搜索树的形状就不同。

在实现时我们要定义一个parent指针,方便新增节点和父结点链接。同时在链接的时候还要判断一下是和父亲的左边链接还是右边链接。

image-20230724163746453

代码实现:

bool Insert(const K& key)
{//确定插入的是否是第一个值,如果是第一个值插入的值就是根结点if (_root == nullptr){_root = new Node(key);//申请一个新节点return true;}//当根不是空时找对应的位置Node* cur = _root;//从根结点开始向后找Node* parent = nullptr;//父结点while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//当相等时返回false,搜索二叉树中不能有相等的else{return false;}}cur = new Node(key);//将其链接到父结点上if (parent->_key > key){parent->_left = cur;}else if (parent->_key < key){parent->_right = cur;}return true;
}	

二叉搜索树的中序遍历

二叉搜索树中序遍历出来的顺序是升序的,我们可以实现一个中序遍历来验证。

void InOrder(Node* root)
{if (root == nullptr){return;}InOrder(root->_left);cout << root->_key << endl;InOrder(root->_right);
}

但是我们发现这个函数调用时候不好处理,因为要传根结点,但是并没有根结点,如果参数没有根,又没办法递归。

所以我们可以套上一层函数:调用无参的函数,当我们调用时调用无参的函数就可以实现中序遍历了。

//套用一层函数
void InOrder()
{_InOrder(_root);
}
//实现中序遍历,中序遍历打印出来是升序的
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

测试一下:

void Test_BSTree()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t1;for (auto e : a){//插入t1.Insert(e);}//中序遍历t1.InOrder();
}
int main()
{Test_BSTree();return 0;
}

打印结果:

image-20230724164737575

二叉搜索树的查找

在二叉搜索树中也可以通和根结点和左右子树的根结点比较查找指定节点值,如果找到返回true,没找到返回false。

代码实现:

//查找接口
bool Find(const K&	key)
{Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return true;}}return false;
}

二叉搜索树的删除

首先查找要删除的元素是否存在,如果不存在,则返回;否则要删除的结点可能分下面三种情况:

a.要删除的结点只有左孩子结点(包含要删除的结点无孩子结点)

b.要删除的结点只有右孩子结点

c.要删除的结点有左、右孩子结点

所以我们针对上面三种情况进行分析:

情况a:要删除的结点只有左孩子结点(包含要删除的结点无孩子结点)

此时删除要删除的结点之后,使被删除节点的父结点指向被删除节点的左孩子结点(直接删除法)

image-20230724171954648

情况b.要删除的结点只有右孩子结点

此时删除要删除的结点之后,使被删除节点的父结点指向被删除节点的右孩子结点(直接删除法)

image-20230724172146756

情况c.要删除的结点有左、右孩子结点

若待删除结点有左、右孩子结点,可以使用替换法进行删除。
可以找到待删除结点左子树中结点值最大的结点,或者是待删除结点右子树中结点值最小的结点来代替待删除结点被删除。代替待删除结点被删除的结点,在左右子树当中至少有一个为空树,那删除该结点之后可以利用上面两种情况来处理。

必须是待删除结点左子树中结点值最大的结点,或者是待删除结点右子树中结点值最小的结点代替待删除结点被删除,只有这样才能保证删除后的二叉树仍保持二叉搜索树的特性。

image-20230724174830726

删除的时候也要用parent记录父结点,保证当被删除结点还有孩子时能够被接管。

代码实现:

bool Erase(const K& key)
{Node* parent = nullptr;//父结点Node* cur = _root;//循环查找while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到该节点,删除该节点else {//如果该结点只有左孩子if (cur->_right == nullptr){//当只有一边时,需要更新_rootif (cur == _root){//左孩子为空,让_root等于右孩子_root = cur->_left;}else{//判断是哪边的,让其接管if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}//删除该结点delete cur;}//该节点只有右孩子else if (cur->_left == nullptr){//当只有右边时,需要更新_rootif (cur == _root){_root = cur->_right;}else {if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_left;}}//删除结点delete cur;}//该节点有左右孩子else{//找右树的最小结点替代//这里不能等于空//Node* pminRight = nullptr;Node* pminRight = cur;Node* minRight = cur->_right;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}//找到右树的最小结点之后再把key传过去cur->_key = minRight->_key;if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;
}

image-20230724193645593

同时我们在实现时应该注意当其为如下特殊情况时,更新_root

image-20230724192934049

二叉搜索树的接口(递归实现)

一般在类里面写递归都要套上一层。

二叉搜索树的插入

要在搜索树里面进行一个插入:比根结点大的值和右子树根结点比较插入;比根结点小的值和左子树根结点比较插入,需要注意插入要和父结点链接起来。我们可以传入父结点,但是这里使用引用是最优的,root_root->left或者_root->right的别名(上一层的别名),就能够链接上。要注意C++的引用不能改变指向,循环里面不能用引用。

递归实现插入代码:

bool _InSertR(Node*& root, const K& key)
{if (root == nullptr){root = new Node(key);return true;}if (root->_key > key){_InSertR(root->_left, key);}if (root->_key < key){_InSertR(root->_right, key);}elsereturn false;
}
bool InSertR(const K& key)
{return _InSertR(_root, key);
}

二叉搜索树的查找

查找,如果在二叉树中返回true,否则返回false。

递归实现查找代码:

//套用一层函数
bool _FindR(Node* root,const K& key) 
{if (root == nullptr){return false;}if (root->_key == key){return true;}if (root->_key > key){return _FindR(root->_left, key);}else{return _FindR(root->_right, key);}
}
bool FindR(const K& key)
{return _FindR(_root,key);
}

二叉搜索树的删除

删除的思路和非递归方式一样,当要删除的结点有左右孩子的时候使用替换法,找左子树的最大值或者右子树的最小值(下面的递归实现采用的是找左子树的最大值),但是注意递归这里不能使用root->_key = maxLeft->_key,如果这样两个值相同了,不能找到要删除的结点。

递归删除函数子函数中必须使用引用接收参数,保证能够链接起来。

root是指针,直接让指针指向其指定结点就可以了,不用找到父节点。要保存一下要删除的结点Node* del = root,不然改变root指针后没办法删除要删除的结点。

bool _EarseR(Node*& root,const K& key)
{if (root == nullptr){return false;}if (root->_key > key){return _EarseR(root->_left, key);}else if (root->_key < key){return _EarseR(root->_right, key);}else {Node* del = root;//保存一下要删除的结点if (root->_left == nullptr)//root是指针,直接让指针指向其指定结点,这时就链接成功了root = root->_right;else if (root->_right == nullptr)root = root->_left;else{//去找左树的最大值Node* maxLeft = root->_left;while (maxLeft->_right){maxLeft = maxLeft->_right;}//找到进行替代,直接交换swap(root->_key, maxLeft->_key);//交换值的时候不能使用root->_key = maxLeft->_key,如果这样两个值相同了,不能找到要删除的结点。return _EarseR(root->_left,key);//转换成在子树去删除}delete del;}
}
bool EarseR(const K& key)
{return _EarseR(_root, key);
}

return _EraseR(root->_left, key);这里不能使用maxLeft,因为要使用引用,maxLeft只是一个局部变量,会出问题的,引用在递归里面又变成别名。

二叉搜索树的拷贝构造

当我们没有实现拷贝构造时候,使用的都是默认拷贝构造函数,属于浅拷贝。

当我们在没有实现析构函数时使用以下代码时并不会出问题:

void Test_BSTree() {int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t1;for (auto e : a){t1.Insert(e);}t1.InOrder();cout << endl;BSTree<int> t2(t1);t2.InOrder();
}

监视窗口如下:

image-20230724203331219

但是当我们实现析构函数之后就会报错:

image-20230724204001640

所以拷贝构造我们要写成深拷贝(推荐使用递归去实现):

主要思想就是先去创建结点,在返回的时候才开始将各个节点链接起来。

从根结点开始,不能使用插入函数,因为插入顺序不一样,形状不一样:

BSTree(const BSTree<K>& t)
{_root = Copy(t._root);
}
Node* Copy(Node* root)
{if (root == nullptr){return nullptr;}Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;
}

二叉搜索树的赋值

利用拷贝构造然后将其根结点交换即可。

BSTree& operator=(const BSTree<K> t)
{swap(_root, t._root);return *this;//支持连续赋值
}

二叉搜索树的析构函数

二叉树搜索树的析构函数我们采用一个后序的递归删除完成:

//析构函数
~BSTree()
{Destroy(_root);_root = nullptr;
}
void Destroy(Node* root)
{if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;
}

也可以在Destroy那里使用引用传参,从而可以不在析构函数那里写_root = nullptr;,直接在Destroy函数实现,因为root就是_root的别名。

~BSTree()
{Destroy(_root);
}
void Destroy(Node*& root)
{if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;
}

二叉搜索树的应用

K模型

K模型,即只有key作为关键码,结构中只需存储key即可,关键码即为需要搜索到的值。

我们在本篇中讲到的构建、查找、插入就属于K模型。

KV模型

KV模型,对于每一个关键码key,都有与之对应的值value,即<key, value>的键值对。

通过一个值查找另一个值:如中英文互译字典、电话号码查询快递信息等。

我们可以通过改一下本篇中的二叉树搜索树来认识一下key-value模型,之前的二叉树搜索树是key模型。

将代码修改,主要修改模板参数,增加一个参数:

namespace kv {template<class K,class V>struct BSTreeNode {BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _value;//构造函数BSTreeNode(const K& key,const V& value):_left(nullptr), _right(nullptr), _key(key),_value(value){}};template<class K, class V>class BSTree {//喜欢在类里进行类型重定义,因为受到类域的限制typedef BSTreeNode<K,V> Node;public://插入成功返回true,插入失败返回falsebool Insert(const K& key,const V& value){if (_root == nullptr){_root = new Node(key,value);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(key,value);if (parent->_key > key){parent->_left = cur;}else {parent->_right = cur;}return true;}bool Erase(const K& key){Node* parent = nullptr;//父结点Node* cur = _root;//循环查找while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到该节点,删除该节点else {//如果该结点只有左孩子if (cur->_right == nullptr){//当只有一边时,需要更新_rootif (cur == _root){//左孩子为空,让_root等于右孩子_root = cur->_left;}else{//判断是哪边的,让其接管if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}//删除该结点delete cur;}//该节点只有右孩子else if (cur->_left == nullptr){//当只有右边时,需要更新_rootif (cur == _root){_root = cur->_right;}else {if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_left;}}//删除结点delete cur;}//该节点有左右孩子else{//找右树的最小结点替代//这里不能等于空//Node* pminRight = nullptr;Node* pminRight = cur;Node* minRight = cur->_right;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}//找到右树的最小结点之后再把key传过去cur->_key = minRight->_key;if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return cur;}}return nullptr;}//套用一层函数void InOrder(){_InOrder(_root);}//实现中序遍历void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root = nullptr;};
}

实现之后我们通过实现一个单词翻译来测试一下:

//测试
void Test_BSTree()
{kv::BSTree<string, string> dict;dict.Insert("sort", "排序");dict.Insert("left", "左边");dict.Insert("right", "右边");dict.Insert("string", "字符串");dict.Insert("insert", "插入");dict.Insert("erase", "删除");string str;while (cin >> str){//kv::BSTreeNode<std::string,std::string>* ret = dict.Find(str);auto ret = dict.Find(str);if (ret){cout << ":" << ret->_value << endl;}else{cout << "无此单词" << endl;}}
}
int main()
{Test_BSTree();return 0;
}

测试结果:

image-20230724212828292

这种程序怎么结束呢?while (cin >> str)ctrl+c是发送终止信号,也可以使用ctrl+z+换行来结束。

我们还可以使用修改后的代码用来测试统计水果出现的次数:

void Test_BSTree()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };kv::BSTree<string, int> countTree;for (auto str : arr){//kv::BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == nullptr){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}
int main()
{Test_BSTree();return 0;
}

测试结果(这里的顺序是按照string数组中出现的先后顺序来排序的):

image-20230724213300066

二叉搜索树性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

image-20230724205939957

对于有N个结点的二叉搜索树,最优的情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN;最差的情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2。

而时间复杂度描述的是最坏情况下算法的效率,因此普通二叉搜索树各个操作的时间复杂度都是O(N)。

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

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

相关文章

近 2000 台 Citrix NetScaler 服务器遭到破坏

Bleeping Computer 网站披露在某次大规模网络攻击活动中&#xff0c;一名攻击者利用被追踪为 CVE-2023-3519 的高危远程代码执行漏洞&#xff0c;入侵了近 2000 台 Citrix NetScaler 服务器。 研究人员表示在管理员安装漏洞补丁之前已经有 1200 多台服务器被设置了后门&#x…

shell之正则表达式及三剑客grep命令

一、正则表达式概述 什么是正则表达式&#xff1f; 正则表达式是一种描述字符串匹配规则的重要工具 1、正则表达式定义: 正则表达式&#xff0c;又称正规表达式、常规表达式 使用字符串描述、匹配一系列符合某个规则的字符串 正则表达式 普通字符&#xff1a; 大小写字母…

【网络编程系列】网络编程实战

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

什么是CSS预处理器?请列举几个常见的CSS预处理器。

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ CSS预处理器是什么&#xff1f;⭐ 常见的CSS预处理器⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是…

《安富莱嵌入式周报》第320期:键盘敲击声解码, 军工级boot设计,开源CNC运动控制器,C语言设计笔记,开源GPS车辆跟踪器,一键生成RTOS任务链表

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV1Cr4y1d7Mp/ 《安富莱嵌入式周报》第320期&#xff1a;键盘敲击…

【STM32】FreeRTOS互斥量学习

互斥量&#xff08;Mutex&#xff09; 互斥量又称互斥信号量&#xff08;本质也是一种信号量&#xff0c;不具备传递数据功能&#xff09;&#xff0c;是一种特殊的二值信号量&#xff0c;它和信号量不同的是&#xff0c;它支持互斥量所有权、递归访问以及防止优先级翻转的特性…

iPhone删除的照片能恢复吗?不小心误删了照片怎么找回?

iPhone最近删除清空了照片还能恢复吗&#xff1f;大家都知道&#xff0c;照片对于我们来说是承载着美好回忆的一种形式。它记录着我们的平淡生活&#xff0c;也留住了我们的美好瞬间&#xff0c;具有极其重要的纪念价值。 照片不小心误删是一件非常难受的事&#xff0c;那么iP…

2023最新水果编曲软件FL Studio 21.1.0.3267音频工作站电脑参考配置单及系统配置要求

音乐在人们心中的地位日益增高&#xff0c;近几年音乐选秀的节目更是层出不穷&#xff0c;喜爱音乐&#xff0c;创作音乐的朋友们也是越来越多&#xff0c;音乐的类型有很多&#xff0c;好比古典&#xff0c;流行&#xff0c;摇滚等等。对新手友好程度基本上在首位&#xff0c;…

仪表板展示 | DataEase看中国:2023年中国电影市场分析

背景介绍 随着《消失的她》、《变形金刚&#xff1a;超能勇士崛起》、《蜘蛛侠&#xff1a;纵横宇宙》、《我爱你》等国内外影片的上映&#xff0c;2023年上半年的电影市场也接近尾声。据国家电影专资办初步统计&#xff0c;上半年全国城市院线票房达262亿元&#xff0c;已经超…

2022年06月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;小白鼠再排队 N只小白鼠(1 < N < 100)&#xff0c;每只鼠头上戴着一顶有颜色的帽子。现在称出每只白鼠的重量&#xff0c;要求按照白鼠重量从小到大的顺序输出它们头上帽子的颜色。帽子的颜色用 “red”&#xff0c;“blue”等字符串来表示。不同的小白…

BUUCTF [MRCTF2020]Ezpop解题思路

题目代码 Welcome to index.php <?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier {protected $var;publi…

基于深度学习的3D城市模型增强【Mask R-CNN】

在这篇文章中&#xff0c;我们描述了一个为阿姆斯特丹 3D 城市模型自动添加门窗的系统&#xff08;可以在这里访问&#xff09;。 计算机视觉用于从城市全景图像中提取有关门窗位置的信息。 由于这种类型的街道级图像广泛可用&#xff0c;因此该方法可用于较大的地理区域。 推荐…

数据结构:栈和队列(超详细)

目录 ​编辑 栈&#xff1a; 栈的概念及结构&#xff1a; 栈的实现&#xff1a; 队列&#xff1a; 队列的概念及结构&#xff1a; 队列的实现&#xff1a; 扩展知识&#xff1a; 以上就是个人学习线性表的个人见解和学习的解析&#xff0c;欢迎各位大佬在评论区探讨&#…

C++入门篇9---list

list是带头双向循环链表 一、list的相关接口及其功能 1. 构造函数 函数声明功能说明list(size_type n,const value_type& valvalue_type())构造的list中包含n个值为val的元素list()构造空的listlist(const list& x)拷贝构造list(InputIterator first, InputIterator…

Python爬虫——scrapy_当当网图书管道封装

创建爬虫项目 srcapy startproject scrapy_dangdang进入到spider文件里创建爬虫文件&#xff08;这里爬取的是青春文学&#xff0c;仙侠玄幻分类&#xff09; srcapy genspider dang http://category.dangdang.com/cp01.01.07.00.00.00.html获取图片、名字和价格 # 所有的se…

快速通过华为HCIP认证

你可以按照以下步骤进行准备和学习&#xff1a; 华为认证课程和资料--提取码:1234https://pan.baidu.com/s/1YJhD8QbocHhZ30MvrKm8hg 了解认证要求&#xff1a;查看华为官方网站上的HCIP认证要求和考试大纲&#xff0c;了解考试的内容、考试形式和考试要求。 学习相关知识&am…

c++ std::shared_ptr(内存布局)

https://cplusplus.com/reference/memory/shared_ptr/

AIGC绘画:kaggle部署stable diffusion项目绘画

文章目录 kaggle介绍项目部署edit my copy链接显示 结果展示 kaggle介绍 Kaggle成立于2010年&#xff0c;是一个进行数据发掘和预测竞赛的在线平台。从公司的角度来讲&#xff0c;可以提供一些数据&#xff0c;进而提出一个实际需要解决的问题&#xff1b;从参赛者的角度来讲&…

泛微E-Office任意文件上传漏洞复现

0x01 产品简介 泛微E-Office是一款标准化的协同 OA 办公软件&#xff0c;泛微协同办公产品系列成员之一,实行通用化产品设计&#xff0c;充分贴合企业管理需求&#xff0c;本着简洁易用、高效智能的原则&#xff0c;为企业快速打造移动化、无纸化、数字化的办公平台。 0x02 漏…

【Linux】Reactor模式

Reactor模式 Reactor模式的定义 Reactor反应器模式&#xff0c;也叫做分发者模式或通知者模式&#xff0c;是一种将就绪事件派发给对应服务处理程序的事件设计模式。 Reactor模式的角色构成 Reactor主要由以下五个角色构成&#xff1a; reactor模式的角色 角色解释Handle(句…