二叉树搜索

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:数据结构——二叉搜索树
☂️<3>开发环境
:Visual Studio 2022
💬<4>前言:在之前的我们已经学过了普通二叉树,了解了基本的二叉树的结构和基本操作了,不过我们之前的二叉树结构都是使用C语言实现的,我们这次来介绍二叉树中更加复杂的树结构,C语言在实现这些结构已经有些吃力了,更适合我们使用C++来实现。

目录

一.前言

二.二叉搜索树

三. 二叉搜索树操作

1.结点与整体结构

2.insert()

 3.find()

4.erase()

 5.构造与析构

四.二叉搜索树的应用

 五. 二叉搜索树的性能分析

六.整体代码


一.前言

map和set特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构,二叉搜索树的特性了解,有助于更好的理解map和set的特性,二叉树中部分面试题稍微有点难度,在前面讲解大家不容易接受,且时间长容易忘。本节借二叉树搜索树,对二叉树部分进行收尾总结。

二.二叉搜索树

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

三. 二叉搜索树操作

1.结点与整体结构

template<class K>
struct BSTreeNode
{BSTreeNode(K key):_key(key),_right(nullptr),_left(nullptr){}K _key;  //数值BSTreeNode<K>* _right;  //右孩子BSTreeNode<K>* _left;   //左孩子
};template<class K>
class BSTree 
{typedef BSTreeNode<K> Node;
public://...
private:Node* _root = nullptr;
};

2.insert()

我们主要针对两种情况:

  1. 二叉树中一个数据都没有
  2. 二叉树已经有数据了

如果二叉树中还没有数据,我们需要将插入的第一个数据作为二叉搜索树的根节点。

如果二叉搜索树中,已经有了数据,我们根据搜索二叉树的特性,如果插入的值比根小,我们就往根的左子树去插入,如果插入的值比根大,我们就往根的右子树去插入,如果遇到相同的值就算是插入失败了,循环上面得动作,直到找到一个空位置。

 循环版本:

    bool insert(const K& key){//如果BSTree还没有结点if (_root == nullptr){_root = new Node(key);return true;}//找到插入的合适位置,和其父亲结点//父亲结点得作用是,我们新插入得结点要和父亲结点连接,//简单来说就是,父亲结点要孩子指针,要指向我们新的结点。Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}//创建新节点cur = new Node(key);//判断新插入得结点是父亲得左孩子还是右孩子if (key > parent->_key){parent->_right = cur;}else{parent->_left = cur;}return true;}

递归版本:

   bool Rinsert(const K& key){return _Rinsert(_root, key);}bool _Rinsert(Node*& root, const K& key){//如果BSTree还没有结点、或者已经找到得合适的空位置if (root == nullptr){root = new Node(key);return true;}//BSTree已经有结点if (key < root->_key){//key比当前结点小,往左树插入return _Rinsert(root->_left, key);}else if (key > root->_key){//key比当前结点大,往右树插入return _Rinsert(root->_right, key);}else{return false;}}//中序遍历void InOrder(){_InOrder(_root);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " " ;_InOrder(root->_right);}

测试:

二叉搜索树,中序遍历得结果就是排序结果,我们可以通过这个特性判断我们插入得是否正确。

int  main()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> b;BSTree<int> Rb;for (auto e : a){b.insert(e);Rb.Rinsert(e);}b.InOrder();cout << endl;Rb.InOrder();return 0;
}

 3.find()

二叉搜索树的查找

  1. 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
  2. 最多查找高度次,走到到空,还没找到,这个值不存在。

找到以后返回目标结点得指针。

 

循环版本:

    Node* find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return cur;}}return nullptr;}

递归版本:

    Node* Rfind(const K& key){return _Rfind(_root, key);}Node* _Rfind(Node*& root, const K& key){if (root == nullptr){return nullptr;}if (key < root->_key){return _Rfind(root->_left, key);}else if (key > root->_key){return _Rfind(root->_right, key);}else{return root;}}

4.erase()

二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否要删除则的结点可能分下面四种情
况:

  • a. 要删除的结点无孩子结点
  • b. 要删除的结点只有左孩子结点
  • c. 要删除的结点只有右孩子结点
  • d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:

  • 情况1:要删除的结点只有左孩子结点,删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
  • 情况2:要删除的结点只有右孩子结点,删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
  • 情况3:要删除的结点有左、右孩子结点,在它的右子树中寻找中序下的第一个结点(数值最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除

 情况二与情况一处理方法相同:

 循环版本:

bool erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{//准备删除//待删除结点,左节点为空,将其右边结点交给父亲if (cur->_left == nullptr){//此时如果删除的是根节点需要改变根节点指向if (cur == _root){_root = _root->_right;}else{//判断待删除结点是父亲的左孩子还是右孩子if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;return true;}//待删除结点,左节点为空,将其左边结点交给父亲else if (cur->_right == nullptr){//此时如果删除的是根节点需要改变根节点指向if (cur == _root){_root = _root->_left;}else{//判断待删除结点是父亲的左孩子还是右孩子if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;return true;}	else{//由于删除的结点左右都有孩子//需要找一个能代替删除结点的位置结点,即比左子树大比右子树小//最适合的结点就是,左子树的最右结点(最大节点),右子树的最左节点(最小结点)Node* MinNode = cur->_right;Node* MinParent = cur;while (MinNode->_left){MinParent = MinNode;MinNode = MinNode->_left;}//先将MinNode结点的孩子交给他的父亲//注意:不能因为是找的最左边结点就因为MinNode结点一定是MinParent的左孩子if (MinParent->_left == MinNode){MinParent->_left = MinNode->_right;}else{MinParent->_right = MinNode->_right;}//将MinNode结点的值赋值给curcur->_key = MinNode->_key;delete MinNode;return true;}}}return false;}

递归版本:

bool _Rerase(Node*& root, const K& key){//空树、没有找到删除的结点if (root == nullptr){return false;}if (key < root->_key){//key比当前结点小,往左树删除return _Rerase(root->_left, key);}else if(key > root->_key){//key比当前结点小,往左树删除return _Rerase(root->_right, key);}else{//找到,开始删除Node* cur = root;if (root->_left == nullptr){//1.待删除结点,左孩子为空root = root->_right;}else if (root->_right == nullptr){//2.待删除结点,右孩子为空root = root->_left;}else//待删除结点,左右孩子都不为空{//找到左树的最大结点Node* maxleft = root->_left;while (maxleft->_right){maxleft = maxleft->_right;}//交换maxleft和待删除结点的Key值,//并再次转换成左树删除一个单孩子结点,复用上述情况一二的代码swap(maxleft->_key, root->_key);return _Rerase(root->_left, key);}delete cur;}}

测试:

int  main()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> b;BSTree<int> Rb;for (auto e : a){b.insert(e);Rb.Rinsert(e);}for (auto e : a){b.erase(e);b.InOrder();cout << endl;Rb.Rerase(e);Rb.InOrder();cout << endl;}return 0;
}

 5.构造与析构

拷贝构造:

前序创建结点,后续连接指向。

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

析构函数:

后续销毁结点

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

默认构造:

如果我们写了拷贝构造,编译器就不会自己生成默认构造函数了,我们可以自己写一个默认构造函数,也可以强制编译器生成一个,但是默认构造只能有一个。

	//告诉编译器强制生成BSTree() = default;//自己写BSTree(){}

四.二叉搜索树的应用

1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方
式在现实生活中非常常见:
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
现次数就是<word, count>就构成一种键值对。

例如:我们将上述的代码改造成K/V结构:

template<class K,class V>
struct BSTreeNode
{BSTreeNode(const K& key, const V& val):_key(key),_val(val),_right(nullptr),_left(nullptr){}K _key;V _val;BSTreeNode<K,V>* _right;BSTreeNode<K,V>* _left;
};template<class K, class V >
class KV_BSTree
{typedef BSTreeNode<K,V> Node;
public:KV_BSTree() = default;KV_BSTree(const KV_BSTree<K,V>& root){_root = _copy(root._root);}~KV_BSTree(){//...}bool insert(const K& key,const V& val){//如果BSTree还没有结点if (_root == nullptr){_root = new Node(key,val);return true;}//找到插入的合适位置,和其父亲结点Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}//判断链接cur = new Node(key,val);if (key > parent->_key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* find(const K& key){//...}bool erase(const K& key){//...}void InOrder(){_InOrder(_root);}private:void Destroy(Node* root){//...}void _InOrder(Node* root){//...}Node* _root = nullptr;
};int main()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };KV_BSTree<string,int> b;for (auto e : arr){auto cur = b.find(e);if (cur == nullptr){b.insert(e, 1);}else{cur->_val++;}}b.InOrder();return 0;
}

 统计水果出现的次数:

 五. 二叉搜索树的性能分析

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

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

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:$log_2 N$
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:$\frac{N}{2}$
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插
入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上
场了。

六.整体代码

BSTree.hpp


#pragma once
#include<iostream>
using namespace std;template<class K>
struct BSTreeNode
{BSTreeNode(const K& key):_key(key),_right(nullptr),_left(nullptr){}K _key;BSTreeNode<K>* _right;BSTreeNode<K>* _left;
};template<class K>
class BSTree 
{typedef BSTreeNode<K> Node;
public://告诉编译器强制生成BSTree() = default;//自己写//BSTree()//{//}BSTree(const BSTree<K>& root){_root = _copy(root._root);}~BSTree(){Destroy(_root);}bool insert(const K& key){//如果BSTree还没有结点if (_root == nullptr){_root = new Node(key);return true;}//找到插入的合适位置,和其父亲结点//父亲结点得作用是,我们新插入得结点要和父亲结点连接,//简单来说就是,父亲结点要孩子指针,要指向我们新的结点。Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}//创建新节点cur = new Node(key);//判断新插入得结点是父亲得左孩子还是右孩子if (key > parent->_key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return cur;}}return nullptr;}bool erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{//准备删除//待删除结点,左节点为空,将其右边结点交给父亲if (cur->_left == nullptr){//此时如果删除的是根节点需要改变根节点指向if (cur == _root){_root = _root->_right;}else{//判断待删除结点是父亲的左孩子还是右孩子if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;return true;}//待删除结点,左节点为空,将其左边结点交给父亲else if (cur->_right == nullptr){//此时如果删除的是根节点需要改变根节点指向if (cur == _root){_root = _root->_left;}else{//判断待删除结点是父亲的左孩子还是右孩子if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;return true;}	else{//由于删除的结点左右都有孩子//需要找一个能代替删除结点的位置结点,即比左子树大比右子树小//最适合的结点就是,左子树的最右结点(最大节点),右子树的最左节点(最小结点)Node* MinNode = cur->_right;Node* MinParent = cur;while (MinNode->_left){MinParent = MinNode;MinNode = MinNode->_left;}//先将MinNode结点的孩子交给他的父亲//注意:不能因为是找的最左边结点就因为MinNode结点一定是MinParent的左孩子if (MinParent->_left == MinNode){MinParent->_left = MinNode->_right;}else{MinParent->_right = MinNode->_right;}//将MinNode结点的值赋值给curcur->_key = MinNode->_key;delete MinNode;return true;}}}return false;}bool Rerase(const K& key){return _Rerase(_root,key);}Node* Rfind(const K& key){return _Rfind(_root, key);}bool Rinsert(const K& key){return _Rinsert(_root, key);}void InOrder(){_InOrder(_root);}private:Node* _copy(const Node* root){if (root == nullptr)return nullptr;Node* newnode = new Node(root->_key);newnode->_left = _copy(root->_left);newnode->_right = _copy(root->_right);return newnode;}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}bool _Rerase(Node*& root, const K& key){//空树、没有找到删除的结点if (root == nullptr){return false;}if (key < root->_key){//key比当前结点小,往左树删除return _Rerase(root->_left, key);}else if(key > root->_key){//key比当前结点小,往左树删除return _Rerase(root->_right, key);}else{//找到,开始删除Node* cur = root;if (root->_left == nullptr){//1.待删除结点,左孩子为空root = root->_right;}else if (root->_right == nullptr){//2.待删除结点,右孩子为空root = root->_left;}else//待删除结点,左右孩子都不为空{//找到左树的最大结点Node* maxleft = root->_left;while (maxleft->_right){maxleft = maxleft->_right;}//交换maxleft和待删除结点的Key值,//并再次转换成左树删除一个单孩子结点,复用上述情况一二的代码swap(maxleft->_key, root->_key);return _Rerase(root->_left, key);}delete cur;}}Node* _Rfind(Node*& root, const K& key){if (root == nullptr){return nullptr;}if (key < root->_key){return _Rfind(root->_left, key);}else if (key > root->_key){return _Rfind(root->_right, key);}else{return root;}}bool _Rinsert(Node*& root, const K& key){//如果BSTree还没有结点if (root == nullptr){root = new Node(key);return true;}//BSTree已经有结点if (key < root->_key){//key比当前结点小,往左树插入return _Rinsert(root->_left, key);}else if (key > root->_key){//key比当前结点大,往右树插入return _Rinsert(root->_right, key);}else{return false;}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " " ;_InOrder(root->_right);}Node* _root = nullptr;
};

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

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

相关文章

[Go版]算法通关村第十四关青铜——原来这就是堆

目录 堆的概念和特征父子关系&#xff1a;(i-1)/2 堆的构造过程自底向上堆化&#xff08;Bottom-up Heapify&#xff09;举例 自顶向下堆化&#xff08;Top-down Heapify&#xff09; 插入操作举例 删除操作举例 堆结构的价值口诀 堆的概念和特征 堆是一个很大的概念&#xff0…

ssm汽车养护管理系统源码和论文

ssm汽车养护管理系统038 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 开题报告内容&#xff1a;&#xff08;研究现状、目的意义&#xff1b;基本内容、研究方法、参考文献等。&#xff09; 研究现状 国外…

driver‘s license exam 4

driver‘s license exam 1_spencer_tseng的博客-CSDN博客 driver‘s license exam 2_spencer_tseng的博客-CSDN博客 driver‘s license exam 3_spencer_tseng的博客-CSDN博客 driver‘s license exam 4_spencer_tseng的博客-CSDN博客 car indicator light_spencer_tseng的博…

java八股文面试[数据结构]——ArrayList和LinkedList区别

ArrayList和LinkedList的异同 二者的线程都不安全&#xff0c;相对线程安全的Vector,执行效率高。此外&#xff0c;ArrayList时实现了基于动态数组的数据结构&#xff0c;LinkedList基于链表的数据结构&#xff0c;对于随机访问get和set&#xff0c;ArrayList觉得优于LinkedLis…

ubuntu上使用osg3.2+osgearth2.9

一、介绍 在ubuntu上使用osgearth加载三维数字地球&#xff0c;首先要有osg和osgearth的库&#xff0c;这些可以直接使用apt-get下载安装&#xff0c;但是版本有些老&#xff0c;如果需要新版本的就需要自己编译。 #查看现有版本 sudo apt-cache madison openscenegraph #安装…

C#实现简单TCP服务器和客户端网络编程

在C#中进行网络编程涉及许多类和命名空间&#xff0c;用于创建和管理网络连接、传输数据等。下面是一些主要涉及的类和命名空间&#xff1a; System.Net 命名空间&#xff1a;这个命名空间提供了大部分网络编程所需的类&#xff0c;包括&#xff1a; IPAddress&#xff1a;用于…

2023年高教社杯数学建模思路 - 复盘:人力资源安排的最优化模型

文章目录 0 赛题思路1 描述2 问题概括3 建模过程3.1 边界说明3.2 符号约定3.3 分析3.4 模型建立3.5 模型求解 4 模型评价与推广5 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 描述 …

第2篇:ESP32 helloword第一个程序示范点亮板载LED

1.选择ESP32开发板 2.寻找串口号&#xff0c;win10自动安装驱动 手动安装驱动参考&#xff1a; 百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可…

ipad可以用别的品牌的手写笔吗?开学平价电容笔推荐

开学需要买什么呢&#xff1f;随着科技的不断进步&#xff0c;各种类型的iPad电容笔应运而生。一支好的电容笔&#xff0c;不仅能大大提高我们的工作效率&#xff0c;而且能大大提高我们的生产力。平替的这款电容笔&#xff0c;不管是在技术上&#xff0c;还是在品质上&#xf…

关于路由器和DNS解析的一些新理解

其实我本人对于交换机和路由器这些网络硬件是比较感兴趣的&#xff0c;也在一点一点的学习相关知识&#xff0c;每次解决一个问题&#xff0c;就让我对一些事情有新的思考。。 今天前台同事&#xff0c;的机器突然上不了网&#xff0c;&#xff0c;和领导一起去看了一波&#…

jmeter CSV 数据文件设置

创建一个CSV数据文件&#xff1a;使用任何文本编辑器创建一个CSV文件&#xff0c;将测试数据按照逗号分隔的格式写入文件中。例如&#xff1a; room_id,arrival_date,depature_date,bussiness_date,order_status,order_child_room_id,guest_name,room_price 20032,2023-8-9 14:…

C++--动态规划两个数组的dp问题

1.最长公共子序列 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串…

Unity 之NavMeshAgent 组件(导航和路径寻找的组件)

文章目录 **作用**&#xff1a;**属性和方法**&#xff1a;**用途**&#xff1a;**注意事项**&#xff1a; NavMeshAgent 是Unity引擎中用于导航和路径寻找的组件。它可以使游戏对象在场景中自动找到可行走的路径&#xff0c;并在避免障碍物的情况下移动到目标位置。 以下是关于…

在当今信息化社会中的安全大文件传输

随着科技的不断进步&#xff0c;数据已经成为各个领域和行业的宝贵财富。然而&#xff0c;随之而来的数据传输和交换问题也成为一个日益突出的挑战。在这篇文章中&#xff0c;我们将探讨在当今信息化社会中的安全大文件传输的重要性&#xff0c;以及如何应对传统传输方式所面临…

穿起“新架构”的舞鞋,跳一支金融数字化转型的华尔兹

华尔兹&#xff0c;是男女两位舞者&#xff0c;通过形体的控制&#xff0c;舞步技巧的发挥&#xff0c;完美配合呈现而出的一种舞蹈形式。华尔兹舞姿&#xff0c;如行云流水、潇洒自如、飘逸优美&#xff0c;素有“舞中皇后”的美称。 在跳华尔兹的时候&#xff0c;如果舞者双…

SQL 盲注

问题描述&#xff1a; 解决方案&#xff1a; 通过建立过滤器方法 添加拦截器&#xff1a; web.xml 文件配置拦截器 <filter><filter-name>sqlFilter</filter-name><filter-class>com.fh.filter.SqlFilter</filter-class></filter> pack…

Python将网络文件下载到本地

Python将网络文件下载到本地 前言相关介绍Python将网络文件下载到本地 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容&#xff0c;可点击进入Python日常小操作专栏、YOLO系列专栏、自然语言处理专栏或我的个人主页查看基于DETR的人脸伪…

【Git版本控制工具使用---讲解一】

Git版本控制工具使用 安装设置用户名签名和邮箱Git常用的命令 初始化本地库查看本地状态Git 命令添加暂存区提交本地库查看版本信息修改文件版本穿梭 安装 首先根据自身电脑的配置选择性的安装是32位的还是64位的Git版本控制工具 我这边安装的是64位的 以下是我安装的时候的过…

springboot2+redis 订阅发布,解决接收消息累计线程到内存溢出,使用自定义线程池接收消息

pom 添加redis <!-- redis 缓存操作 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 发布消息 import lombok.extern.slf4j.Slf4j; import o…