C++《二叉搜索树》
在初阶数据结构中我学习了树基础的概念以及了解了顺序结构的二叉树——堆和链式结构二叉树该如何实现,那么接下来我们将进一步的学习二叉树,在此会先后学习到二叉搜索树、AVL树、红黑树;通过这些的学习将让我们更易于理解后面set、map、哈希等的使用以及对底层结构的了解。在此先本篇中我们将了解二次搜索树的概念以及实现二叉搜索树插入、删除等的操作,在了解了这些之后相信在下一篇的set和map的学习你将轻松许多,接下来就开始本篇的学习吧!!!

1.二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是⼀棵空树,或者是具有以下性质的二叉树:
• 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值
• 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值
• 它的左右子树也分别为二叉搜索树
• 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值
例如以下左边图示的就是不支持插入相等值得二叉搜索树,右边就是支持插入相等值得二叉搜索树

2. 二叉搜索树的性能分析
最好得情况下,在此在二叉搜索树当中最为完全二叉树(或者接近完全二叉树),其高度为:
例如以下示例:

最差情况下,在此⼆叉搜索树退化为单支树(或者类似单支),其高度为:
例如以下示例:

那么通过以上对二叉树最好和最坏情况的分析就可以得出综合而言二叉搜索树增删查改时间复杂度为:
那么通过以上的分析可以看出二叉搜索树这样的效率显然无法满足我们需求的,我们后续需要继续讲解二叉搜索树的变形,平衡二叉搜索树AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。
在此你可能会想到在二叉搜索树中查找数据不就类似与二分查找吗,那么直接使用之前学习的二分查找不就可以了吗,二分查找的效率相比二叉搜索树还更好,那为什么还要学习了解二叉搜索树呢?
在此二分查找也可以实现 O(logN) 级别的查找效率,但是二分查找有两大缺陷:
1. 需要存储在支持下标随机访问的结构中,并且有序。
在使用到二分查找示我们使用的是数据对应的下标来实现查找,这就使得当被查找的一系列数据不是存储在数组里时就需要先将数据都存储在数组当中并且还要将数据排序成升序,在这个过程中就会有时间和空间上的损耗了
2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据⼀般需要挪动数据。
在二分查找当中以上提到的缺点其实还不是最致命的,最要命的是当一组数据已经存储在数组当中时并且已经排序好时,如果之后我们要在这组数据当中再插入新的元素或者是要将原数据中的一个元素删除,那么通过之前顺序表的学习我们就知道每插入或者是删除一个元素时间复杂度都为O(N),若多次进行操作这就使得时间复杂度非常高了
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
通过以上的分析这里也就体现出了平衡二叉搜索树的价值,在插入元素或者是删除元素都相比使用二分查找要有优势。
3. 二叉搜索树的插入
3.1插入分析
在此在二叉搜索树当中插入新的节点就要按照以下步骤进行分析:
1. 树为空,则直接新增结点,赋值给root指针
2. 树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要⼀会往右走,⼀会往左走)
例如以下示例:
假设我们要将以下的数组元素依次插入到二叉搜索树当中
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
所以元素插入完之后二叉搜索树的形式就如下所示:

这时如果我们要再插入值为16的元素在二叉搜索树中过程图就如下所示:

再插入值为3的元素在二叉搜索树中过程图就如下所示:

3.2 插入代码实现
在实现二叉搜索树的插入代码之前我们先要来实现二叉搜索树大体的结构代码
在此我们先创建一个BSTree.h的头文件在该文件当中来实现二叉搜索树的结构以及各个功能,再创建一个test.cpp的文件用于测试我们实现的二叉搜索树的各个功能是否能满足要求
实现了文件的创建之后接下来就来实现二叉搜索树的大体结构。在此由于二叉搜索树是由各个节点构成的,那么和之前实现链式结构的二叉树一样先要实现表示节点的结构体
#include<iostream> using namespace std;template<class K> struct BSTreeNode {K _key;BSTreeNode<K>* left;BSTreeNode<K>* right;BSTreeNode(const K& key):_key(key),left(nullptr),right(nullptr){} };在此就创建一个结构体BSTreeNode来表示二叉树内节点,在节点当中有三个变量分别是_key表示节点内的数据、_left存储该节点左孩子节点的指针、_right存储该节点右孩子节点的指针。并且将该结构体实现成模板这样就可以支持节点内的元素是任意类型的数据,还有在结构体当中还实现了构造函数。
实现了节点的结构体之后接下来就可以来实现表示二叉搜索树的类,在此我们将类命名为BSTree,实现的是模板类,实现的大体结构如下所示:
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;public://使用编译器生成的默认构造函数BSTree() = default;//实现各种功能的成员函数……private://头节点Node* _root = nullptr;};
完成了以上操作接下来就可以来实现插入函数的代码了
注:以下实现的插入函数是数据不支持冗余的情况也就是二叉树当中不支持插入相等的值
bool Insert(const K& key)
{//当根节点为空时if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;//节点的父节点Node* parentcur = nullptr;while (cur){//当key小于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}当key大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return false;}}cur = new Node(key);//当新节点内的值大于父节点内的值时if (parentcur->_key < key){parentcur->right = cur;}//当新节点内的值小于父节点内的值时else{parentcur->left = cur;}return true;}
4. 二叉搜索树的查找
4.1查找分析
在二叉搜索树中查找节点就要按照以下步骤进行分析:
1. 从根开始比较,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。
2. 最多查找高度次,走到到空,还没找到,这个值不存在。
3. 如果不支持插入相等的值,找到x即可返回
4. 如果支持持插入相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。
例如以下示例:
当要查找3时,要找到1的右孩子的那个3值返回

4.2 查找代码实现
在进行了二叉搜索树的节点查找的分析之后接下来我们就来实现查找代码
注:以下实现的查找函数是数据不支持冗余的情况也就是二叉树当中不支持插入相等的值
Node* Find(const K& key)
{//当根结点为空时if (_root == nullptr){return nullptr;}Node* cur = _root;Node* parentcur = nullptr;while (cur){//当key大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key小于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return cur;}}//当前二叉树中找不到值为key的节点return nullptr;
}
5. 二叉搜索树的删除
5.1 删除分析
在二叉搜索树当中节点的删除相比插入和查找就复杂一些了,在此会出现以下的多种情况接下来就来一一分析
在此要删除的节点会有以下的四种情况:
1.要删除结点N左右孩子均为空
当要删除节点的节点左右孩子节点都为空时就需要把N结点的父亲对应孩子指针指向空,直接删除N结点
例如以下示例:

在以上二叉搜索树当中要删除值为1的节点就需要将节点1删除之后再将其父节点的左孩子节点指向空
2. 要删除的结点N左孩子位空,右孩子结点不为空
当要删除节点的节点左孩子节点为空时就需要把N结点的父亲对应孩子指针指向N的右孩子,之后直接删除N结点
例如以下示例:

在以上二叉搜索树当中要删除值为10的节点就需要将其父节点的左孩子变为原节点的右孩子节点之后再将节点10删除
3.要删除的结点N右孩子位空,左孩子结点不为空
当要删除节点的节点右孩子节点为空时就需要把N结点的父亲对应孩子指针指向N的左孩子,之后直接删除N结点
例如以下示例:

在以上二叉搜索树当中要删除值为14的节点就需要将其父节点的左孩子变为原节点的左孩子节点之后再将节点14删除
4. 要删除的结点N左右孩子结点均不为空
当要删除的节点左右孩子节点都不为空时,这是就不能像以上的情况一样简单的改变要删除节点的父节点指针,这时由于无法直接删除N结点,这是因为N的两个孩⼦无处安放,只能用替换法删除。在此根据二叉搜索树的性质就需要找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的位置,都满足⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。
例如以下示例:

在以上二叉搜索树当中要删除节点值为8的节点和值为3的节点,由于这两个节点都是左右孩子节点都不为空的节点,因此要删除值为8的节点就需要找到其左子树最右的节点或者是右子树最左的节点(在此我们是找右子树最左的节点)

之后交换要删除的节点和找出的节点的值

最后删除交换之后值为8的节点即可

注:要删除值为3的节点和以上的方式也类型在此就不再细致的讲解
5.2 删除代码实现
bool Erase(const K& key)
{//当根节点为空时if (_root == nullptr){return false;}//当前节点Node* cur = _root;//当前节点的父节点Node* parentcur = _root;while (cur){//当key的值大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key的值大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key的值等于当前节点的值else{//当要删除的节点左孩子节点为空if (cur->left==nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Right = cur->right;delete cur;_root = Right;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->right;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->right;delete cur;}}return true;}//当要删除的节点右孩子节点为空else if (cur->right==nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Left = cur->left;delete cur;_root = Left;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->left;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->left;delete cur;}}return true;}//当要删除的节点左右孩子节点都为空else{Node* minrightParent = cur;Node* minright = cur->right;//找出当前节点右子树中的最左节点while (minright->left){minrightParent = minright;minright = minright->left;}cur->_key = minright->_key;//当最左节点为其父节点的左节点时if (minrightParent->left == minright){minrightParent->left = minright->right;}//当最左节点为其父节点的右节点时else {minrightParent->right = minright->right;}delete minright;return true; }}}//当找不到要删除的节点就返回falsereturn false;}
6. 二叉搜索树遍历
由于二叉搜索树的性质一棵二叉搜索树中序遍历输出的结果就是递增的,那么接下来我们就试着来实现中序遍历的代码
void Inorder()
{_Inorder(_root);cout << endl;
}void _Inorder(Node* root)
{if (root == nullptr){return;}_Inorder(root->left);cout << root->_key <<" ";_Inorder(root->right);
}
在此我们实现的中序遍历代码如上所示,那么这时你可能就会有疑问了,为什么要实现两个中序遍历的函数,不是直接使用一个函数就可以满足要求了吗?
在此要考虑到的是在BsTree类以外用户是无法得到二叉搜索树的根节点的,但是在调用中序遍历的函数根据之前我们使用递归的方式是需要一开始就需要将二叉树的根结点作为中序遍历函数的参数的。因此为了解决该问题就再在BSTree类内实现一个函数来调用中序遍历的成员函数,由于是在类的内部在此是可以访问私有的成员变量的,在类外部用户要使用中序遍历时就只需要调用无参的成员函数Inorder就可以得到二叉搜索树中序遍历的结果了。
7. 二叉搜索树完整代码
#include<iostream>
using namespace std;template<class K>
struct BSTreeNode
{K _key;BSTreeNode<K>* left;BSTreeNode<K>* right;BSTreeNode(const K& key):_key(key), left(nullptr), right(nullptr){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;public://使用编译器生成的默认构造函数BSTree() = default;bool Insert(const K& key){//当根节点为空时if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;//节点的父节点Node* parentcur = nullptr;while (cur){//当key小于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}当key大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return false;}}cur = new Node(key);//当新节点内的值大于父节点内的值时if (parentcur->_key < key){parentcur->right = cur;}//当新节点内的值小于父节点内的值时else{parentcur->left = cur;}return true;}Node* Find(const K& key){//当根结点为空时if (_root == nullptr){return nullptr;}Node* cur = _root;Node* parentcur = nullptr;while (cur){//当key大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key小于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return cur;}}//当前二叉树中找不到值为key的节点return nullptr;}bool Erase(const K& key){//当根节点为空时if (_root == nullptr){return false;}//当前节点Node* cur = _root;//当前节点的父节点Node* parentcur = _root;while (cur){//当key的值大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key的值大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key的值等于当前节点的值else{//当要删除的节点左孩子节点为空if (cur->left == nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Right = cur->right;delete cur;_root = Right;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->right;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->right;delete cur;}}return true;}//当要删除的节点右孩子节点为空else if (cur->right == nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Left = cur->left;delete cur;_root = Left;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->left;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->left;delete cur;}}return true;}//当要删除的节点左右孩子节点都为空else{Node* minrightParent = cur;Node* minright = cur->right;//找出当前节点右子树中的最左节点while (minright->left){minrightParent = minright;minright = minright->left;}cur->_key = minright->_key;//当最左节点为其父节点的左节点时if (minrightParent->left == minright){minrightParent->left = minright->right;}//当最左节点为其父节点的右节点时else{minrightParent->right = minright->right;}delete minright;return true;}}}//当找不到要删除的节点就返回falsereturn false;}void Inorder(){_Inorder(_root);cout << endl;}private://头节点Node* _root = nullptr;void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->left);cout << root->_key << " ";_Inorder(root->right);}};
8. 二叉搜索树key和key/value使用场景
8.1 key搜索场景
key搜索场景的形式如下所示:
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。
在以上我们了解实现的二叉搜索树其实是适用于key的场景,那么接下来就来看看那么场景是属于key的场景
场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,⻋辆进入时扫描⻋牌在不在系统中,在则抬杆,不在则提示非本小区⻋辆,无法进入。

场景2:检查⼀篇英文文章单词拼写是否正确,将词库中所有单词放入二叉搜索树,读取文章中的单词,查找是否在二叉搜索树中,不在则波浪线标红提示。

8.2 key/value搜索场景
key/value搜索场景的形式如下所示:
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的二叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树结构了,可以修改value。
接下来就来将以上我们实现的key二叉搜索树修改为key/value⼆叉搜索树代码,实现代码如下所示:
#include<iostream>
using namespace std;template<class K,class V>
struct BSTreeNode
{K _key;V _value;BSTreeNode<K ,V>* left;BSTreeNode<K ,V>* right;BSTreeNode(const K& key, const V& value):_key(key);_value(value), left(nullptr), right(nullptr){}
};template<class K,class V>
class BSTree
{typedef BSTreeNode<K,V> Node;public://使用编译器生成的默认构造函数BSTree() = default;bool Insert(const K& key, const V& value){//当根节点为空时if (_root == nullptr){_root = new Node(key,value);return true;}Node* cur = _root;//节点的父节点Node* parentcur = nullptr;while (cur){//当key小于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return false;}}cur = new Node(key, value);//当新节点内的值大于父节点内的值时if (parentcur->_key < key){parentcur->right = cur;}//当新节点内的值小于父节点内的值时else{parentcur->left = cur;}return true;}Node* Find(const K& key){//当根结点为空时if (_root == nullptr){return nullptr;}Node* cur = _root;Node* parentcur = nullptr;while (cur){//当key大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key小于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key等于当前节点的值else{return cur;}}//当前二叉树中找不到值为key的节点return nullptr;}bool Erase(const K& key){//当根节点为空时if (_root == nullptr){return false;}//当前节点Node* cur = _root;//当前节点的父节点Node* parentcur = _root;while (cur){//当key的值大于当前节点的值if (cur->_key < key){parentcur = cur;cur = cur->right;}//当key的值大于当前节点的值else if (cur->_key > key){parentcur = cur;cur = cur->left;}//当key的值等于当前节点的值else{//当要删除的节点左孩子节点为空if (cur->left == nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Right = cur->right;delete cur;_root = Right;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->right;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->right;delete cur;}}return true;}//当要删除的节点右孩子节点为空else if (cur->right == nullptr){//当要删除的节点为根结点时直接改变_root指针的指向,之后再将原根结点释放if (cur == _root){Node* Left = cur->left;delete cur;_root = Left;}else{//当要删除的节点cur是其父节点的左节点时if (cur == parentcur->left){parentcur->left = cur->left;delete cur;}//当要删除的节点cur是其父节点的右节点时else if (cur == parentcur->right){parentcur->right = cur->left;delete cur;}}return true;}//当要删除的节点左右孩子节点都为空else{Node* minrightParent = cur;Node* minright = cur->right;//找出当前节点右子树中的最左节点while (minright->left){minrightParent = minright;minright = minright->left;}cur->_key = minright->_key;//当最左节点为其父节点的左节点时if (minrightParent->left == minright){minrightParent->left = minright->right;}//当最左节点为其父节点的右节点时else{minrightParent->right = minright->right;}delete minright;return true;}}}//当找不到要删除的节点就返回falsereturn false;}void Inorder(){_Inorder(_root);cout << endl;}private://头节点Node* _root = nullptr;void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->left);cout << root->_key << ":" << root->_val << " ";_Inorder(root->right);}};
在以上我们了解实现的二叉搜索树其实是适用于key的场景,那么接下来就来看看那么场景是属于key的场景
场景1:商场无人值守车库,入⼝进场时扫描⻋牌,记录车牌和入场时间,出口离场时,扫描牌,查找入场时间,用当前时间-⼊场时间计算出停⻋时长,计算出停车费用,缴费后抬杆,车辆离场。

场景1:简单中英互译字典,树的结构中(结点)存储key(文)和vlaue(中文),搜索时输⼊英文,则同时查找到了英文对应的中文。
场景3:统计⼀篇文章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。
以下的两个简单的示例就是使用二叉搜索树的key/val来解决
#include"BSTree.h"int main()
{//示例1
//将以下英文单词和对应的中文翻译绑定到一起,当用户输入输入正确的单词就输出其中文意思,否则就输出单词拼写错误BSTree<string, string> dict;dict.Insert("insert", "插入");dict.Insert("erase", "删除");dict.Insert("left", "左边");dict.Insert("string", "字符串");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_val << endl;}else{cout << "单词拼写错误" << endl;}}//示例2
//统计字符串数组当中各个水果的出现次数string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };// 统计水果出现的次BSTree<string, int> countTree;for (auto str : strs){auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);}else{ret->_val++;}}countTree.Inorder();return 0;
}
以上就是本篇的区别内容了,希望能得到你的点赞和收藏 ❤️
相关文章:
C++《二叉搜索树》
在初阶数据结构中我学习了树基础的概念以及了解了顺序结构的二叉树——堆和链式结构二叉树该如何实现,那么接下来我们将进一步的学习二叉树,在此会先后学习到二叉搜索树、AVL树、红黑树;通过这些的学习将让我们更易于理解后面set、map、哈希等…...
机器学习-神经网络(BP神经网络前向和反向传播推导)
1.1 神经元模型 神经网络(neural networks)方面的研究很早就已出现,今天“神经网络”已是一个相当大的、多学科交叉的学科领域.各相关学科对神经网络的定义多种多样,本书采用目前使用得最广泛的一种,即“神经网络是由具有适应性的简单单元组成的广泛并行互连的网络,它的组织能够…...
基于智能物联网关的车辆超重AI检测应用
超重超载是严重的交通违法行为,超重超载车辆的交通安全风险极高,像是一颗行走的“不定时炸弹”,威胁着社会公众的安全。但总有一些人受到利益驱使,使超重超载的违法违规行为时有发生。 随着物联网和AI技术的发展,针对预…...
记录pbootcms提示:登录失败:表单提交校验失败,请刷新后重试的解决办法
问题描述 pbootcms后台登录的时候提示“登录失败:表单提交校验失败,请刷新后重试!” 解决办法 删除runtime目录,或尝试切换PHP版本,选择7.3或5.6一般就能解决了。...
【JavaScript】同步异步详解
同步和异步是编程中处理任务执行顺序的两种不同方式。理解这两种概念对于编写高效和响应式的应用程序至关重要。 同步(Synchronous) 定义:同步操作是指一个任务必须在下一个任务开始之前完成。换句话说,代码按顺序执行ÿ…...
vue 使用el-button 如何实现多个button 单选
在 Vue 中,如果你想要实现多个 el-button 按钮的 单选(即只能选择一个按钮),可以通过绑定 v-model 或使用事件来处理按钮的选中状态。 下面是两种实现方式,分别使用 v-model 和事件监听来实现单选按钮效果:…...
HarmonyOS-初级(二)
文章目录 应用程序框架UIAbilityArkUI框架 🏡作者主页:点击! 🤖HarmonyOS专栏:点击! ⏰️创作时间:2024年11月28日13点10分 应用程序框架 应用程序框架可以被看做是应用模型的一种实现方式。 …...
Unity开启外部EXE程序
Unity开启外部EXE using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; using UnityEditor; using UnityEngine;public class Unity_OpenExe : Mono…...
CTF之密码学(埃特巴什码 )
一、基本原理 埃特巴什码的原理是:字母表中的最后一个字母代表第一个字母,倒数第二个字母代表第二个字母,以此类推。在罗马字母表中,对应关系如下: 常文(明文):A B C D E F G H I …...
深入解析 PyTorch 的 torch.load() 函数:用法、参数与实际应用示例
深入解析 PyTorch 的 torch.load() 函数:用法、参数与实际应用示例 函数 torch.load() 是一个在PyTorch中用于加载通过 torch.save() 保存的序列化对象的核心功能。这个函数广泛应用于加载预训练模型、模型的状态字典(state dictionaries)、…...
ros2键盘实现车辆: 简单的油门_刹车_挡位_前后左右移动控制
参考: ROS python 实现键盘控制 底盘移动 https://blog.csdn.net/u011326325/article/details/131609340游戏手柄控制 1.背景与需求 1.之前实现过 键盘控制 底盘移动的程序, 底盘是线速度控制, 效果还不错. 2.新的底盘 只支持油门控制, 使用线速度控制问题比较多, 和底盘适配…...
ubuntu安装chrome无法打开问题
如果在ubuntu安装chrome后,点击chrome打开没反应,可以先试着在terminal上用命令打开 google-chrome 如果运行命令显示 Chrome has locked the profile so that it doesnt get corrupted. If you are sure no other processes are using this profile…...
CTF-RE 从0到N:Chacha20逆向实战 2024 强网杯青少年专项赛 EnterGame WP (END)
只想解题的看最后就好了,前面是算法分析 Chacha20 c语言是如何利用逻辑运算符拆分变量和合并的 通过百度网盘分享的文件:EnterGame_9acdc7c33f85832082adc6a4e... 链接:https://pan.baidu.com/s/182SRj2Xemo63PCoaLNUsRQ?pwd1111 提取码:1…...
vue3 ajax获取json数组排序举例
使用axios获取接口数据 可以在代码中安装axios包,并写入到package.json文件: npm install axios -S接口调用代码举例如下: const fetchScore async () > {try {const res await axios.get(http://127.0.0.1:8000/score/${userInput.v…...
web安全之信息收集
在信息收集中,最主要是就是收集服务器的配置信息和网站的敏感信息,其中包括域名及子域名信息,目标网站系统,CMS指纹,目标网站真实IP,开放端口等。换句话说,只要是与目标网站相关的信息,我们都应该去尽量搜集。 1.1收集域名信息 知道目标的域名之后,获取域名的注册信…...
报错:java: 无法访问org.springframework.boot.SpringApplication
idea报错内容: java: 无法访问org.springframework.boot.SpringApplication 报错原因: <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4…...
线上+线下≠新零售,6大互通诠释新零售的核心要点-亿发
新零售,这个词汇在近年来频繁出现在我们的视野中,它不仅仅是线上与线下的简单相加,而是一场深刻的商业变革。本文将通过6大互通的核心要点,为您揭示新零售的真正内涵。 1. 商品的互联互通 新零售模式下,商品的互联互…...
GitHub Copilot革命性更新:整合顶尖AI模型,如何重塑开发体验?
在技术快速发展的今天,代码辅助工具已成为提升开发效率的利器。今天,我们带来了一个激动人心的消息——GitHub Copilot宣布引入多模型选择功能,这不仅是技术上的一次飞跃,更是对开发者工作流程的一次革新。 多模型选择:…...
AWS账户是否支持区域划分?
在云计算的世界中,亚马逊网络服务(AWS)凭借其全球化的基础设施和丰富的服务选项受到许多企业和开发者的青睐。一个常见的问题是:AWS账户是否支持区域划分?为了回答这个问题,我们九河云一起深入了解AWS的区域…...
Easy Excel 通过【自定义批注拦截器】实现导出的【批注】功能
目录 Easy Excel 通过 【自定义批注拦截器】实现导出的【批注】功能需求原型:相关数据:要导出的对象字段postman 格式导出对象VO 自定义批注拦截器业务代码: 拦截器代码解释:详细解释:格式优化: Easy Excel…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
