【数据结构】搜索二叉树(C++实现)

news/2024/4/19 8:38:36/文章来源:https://blog.csdn.net/Brant_zero/article/details/127657063

目录

一、二叉搜索树的概念

二、二叉搜索树的实现

2.1 节点的定义及构造

2.2 树的结构及功能展示

2.3 树的 Insert

2.4 树的中序遍历

2.4 树的 Find

2.5 树的 Erase

2.6 拷贝构造、赋值运算符重载、析构函数

三、递归实现树的增删查 

3.1 递归实现 FindR

3.2 递归实现 InsertR

3.3 递归实现 EraseR

四、二叉树搜索树的应用

4.1 key 模型

4.2 key-value 模型

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

六、二叉搜索树(key、key-value)代码


一、二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
  • 若它的左子树不为空,则左子树上所有的节点的值都小于根节点的值。
  • 若它的右子树不为空,则右子树上所有的节点的值都大于根节点的值。
  • 它的左右子树也分别为二叉搜索树。

二、二叉搜索树的实现

2.1 节点的定义及构造

创建树之前必然要先定义好节点,跟之前普通链式二叉树没有什么区别。

注意:

①我们会不断的通过 key 创建树节点, new 出的对象会调用带参的构造函数。所以,我们定义好节点中的成员变量后还要书写好构造函数。

②因为树节点会频繁访问成员变量,所以我们要将其置为公有成员(public),如果觉得麻烦,可以直接使用 struct 定义节点。

template <class K>
class BSTreeNode
{	
public:BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;//用 key 构造一个树节点BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}
};

2.2 树的结构及功能展示

接下来看看 BSTree 类中的我们要实现的成员函数及变量定义。

template <class K>class BSTree{typedef BSTreeNode<K> Node;private:Node* _root = nullptr;public://默认成员函数BSTree ();BSTree (const K& key);BSTree& operator=(BSTree Tree);~BSTree();bool Insert(const K& key);void Inorder();bool find(const K& key);bool Erase(const K& key);//递归实现   bool FindR(const K& key);bool InsertR(const K& key);bool EraseR(const K& key);}

2.3 树的 Insert

首先,我们实现树的插入。我们要明确,这个插入要符合二叉搜索树的特性,即左子树的值小于根节点的值,右节点的值都大于根节点的值。

共分为以下几种情况和步骤:

  1. 传入空树直接 new 一个节点,将其置为 root 。
  2. 找到 key 值该在的位置,如果 key 大于 当前节点的 _key,则往右走,小于则往左走。
  3. 如果 key 等于当前节点的 _key,直接返回 false。
  4. 直到 cur 走到空,此时 cur 指向的便是 key 应当存放的地方。
  5. 创建一个节点并链接到树中(链接到 parent 节点的左或右)
	bool Insert(const K& key){//如果当前树为空if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;//直到 cur 指向 nullptrwhile (cur){//cur->_key 小于 key 走右子树if (cur->_key < key){parent = cur;cur = cur->_right;}//cur->_key 小于  走左子树else if (cur->_key > key){parent = cur;cur = cur->_left;}//cur->_key == key 不允许插入else{return false;}}//此时cur处于正确的位置。cur = new Node(key);//判断 key 应该在 parent 的左边还是右边if (parent->_key > key)parent->_left = cur;elseparent->_right = cur;return true;}

2.4 树的中序遍历

插入完成后,接下来我们就测试一下我们的代码。

因为搜索树的规律为,左子树<根节点<右子树。所以说我们只要先打印左子树,在打印根节点,最后打印右子树,就可以按顺序输出树中存放的 key 值。

而这个顺序正好对应我们二叉树中的中序遍历。接下来我们就来实现一个中序遍历吧。

类中的递归函数并不容易被调用。如果我们直接使用 root 作为函数的 Insert 的参数,就不得不将_root 变为 公有成员

代码检测:

2.4 树的 Find

find 和 Insert 核心代码完全相同。

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

效果检测:

2.5 树的 Erase

Erase 删除分为以下三种情况:

  1. 删除节点为叶子节点
  2. 删除节点有一个子节点
  3. 删除节点有两个子节点

情况一和情况二非常好解决,其本质都属于左或右节点为空,当该节点只有一个孩子或无孩子时,直接让 parent 指向该节点子节点,然后将此节点移除出树。 

我们先来解决前两种情况。其中有这几个点需要我们注意:

1.删除的是根节点

如果parent指向的是nullptr,则直接让_root后移动即可。

2. 链接时,应该链接到父亲的左还是右。

如果parent的左边是待删节点,即parent->left==cur,则将cur的右边链接到parent的左边

如果parent的右边是待删节点,即parent->right==cur,将cur的右边链接到parent的右边

代码如下(情况1、2的解决方法):

bool _Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;//找keywhile (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到存放 key的节点else{//key的左子树为空 所以父节点链接右子树if (cur->_left == nullptr){//如果删除的是根节点,此时父节点指向为nullptrif (parent == nullptr){//直接让_root指向下一个节点_root = _root->_right;}else{//判断应该链接到父节点的左还是右if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}//key的右子树为空 所以父节点链接左子树else if (cur->_right == nullptr){//删除的为根节点的情况if (parent == nullptr){_root = _root->_left;}else{//判断应当链接到父节点的左还是右if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}//删除节点左、右都不为空else{}return true;}}return false;
}

情况三采用的是替换法删除,我们观察下图,删除 3 的情况。 

为什么会这样呢?我们结合搜索树的性质和中序遍历,中序遍历中打印根节点的上一个节点是左子树的最右节点,根节点的下一个节点是右子树的最左节点,这两个节点的值于根节点的值最相近,所以,这两个节点是替换根节点的最好节点,替换后能不破坏搜索树的结构。 

我们把 3 看作根为一棵搜索树

  • 左子树的最大节点——左子树的最右节点,即 2
  • 右子树的最小节点——右子树的最左节点,即 4

所以这里我们找右子树的最小节点进行替换。

此时我们开始编写代码,不考虑一些特殊情况。

//删除节点左、右都不为空else{Node* min = cur->_right;while (cur->_right == nullptr){min = min->_left;}swap(cur->_key, cur->_key);delete cur;}

 好的,接下来我们分析上面的代码会造成什么问题。

1. 此时 6 节点的_left 仍然指向原来的 4 节点,出现野指针的问题。并且如果 4 节点还有右子树呢?如图:

2. 无法删除根节点(8)

解决问题1,我们的方法是仍要记录下min的父节点,让父节点指向 min->right,此时无论min->right有子树还是min->right==nullptr,都可以很好的解决该问题,代码如下:

解决问题2:

删除8节点,此时 min 指向了cur->right,min ->left 为空,没有进入循环,导致minparent 为空指针,指向 minparent->_left = min->right;出现非法访问。

所以我们要将minparent初始化为cur。如果删除8节点,min节点往下找右子树的最左节点,再让 parent 指向右子树的最左节点的右子树,此时就会破坏树的结构,如图:

 所以,我们还是要判断,如果 min 在 minparent 的左子树,就改变minparent的左子树;如果 min在minparent的右子树,就改变minparent的右子树。

 

 好的,这两个棘手的问题我们就顺利解决了,我们来看看整体的 Erase 代码。

//删除
bool _Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;//找keywhile (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到存放 key的节点else{//key的左子树为空 所以父节点链接右子树if (cur->_left == nullptr){//如果删除的是根节点,此时父节点指向为nullptrif (parent == nullptr){//直接让_root指向下一个节点_root = _root->_right;}else{//判断应该链接到父节点的左还是右if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}//key的右子树为空 所以父节点链接左子树else if (cur->_right == nullptr){//删除的为根节点的情况if (parent == nullptr){_root = _root->_left;}else{//判断应当链接到父节点的左还是右if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}//删除节点左、右都不为空else{//指向cur,防止非法访问Node* minparent = cur;Node* min = cur->_right;while (min->_left != nullptr){minparent = min;min = min->_left;}swap(cur->_key, min->_key);//解决野指针或min->right有子树的情况//minparent->_left = min->_right;//判断min在minparent的左或右if (minparent->_left == min)minparent->_left = min->_right;elseminparent->_right = min->_right;delete min;}return true;}}return false;
}

好的,我们来测试一下代码。

2.6 拷贝构造、赋值运算符重载、析构函数

析构函数

与普通的二叉树Destory代码几乎一样。

构造与拷贝构造函数函数

拷贝构造函数就是根据前序构造出一棵树,如下:

注意,如果写了默认拷贝构造函数编译器就不会默认生成构造函数了,所以这里我们也要提供一个默认构造函数或者强制编译器为我们默认生成一个构造函数。如下:

 

赋值运算符重载

因为我们已经实现了拷贝构造函数,所以我们可以套用拷贝构造函数来实现赋值运算符重载。

 测试:

三、递归实现树的增删查 

3.1 递归实现 FindR

实现递归版本的 Find ,总共分4步:

  1. 如果 root 指向为 nullptr ,说明未找到 key 值
  2. 如果 key 大于 root->key,说明 key 在 root 的右边,使用root->right继续递归。
  3. 如果 key 小于 root->key,说明 key 在 root 的左边,使用root->left继续递归。
  4. 最后就是 key==root->key 的情况,返回 true 。

代码如下:

//Find的递归版本
bool FindR(const K& key)
{return _FindR(_root, key);
}bool _FindR(const Node* root, const K& key)
{if (root == nullptr)return false;if (root->_key < key){_FindR(root->_right, key);}else if (root->_key > key){_FindR(root->_left, key);}else{return true;}
}

3.2 递归实现 InsertR

在实现了FindR之后,实现出InsertR应该不难,代码大致如下:


bool InsertR(const K& key)
{return _InsertR(_root, key)
}bool _InsertR(const Node* root, const K& key)
{if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{//已存在 key 不允许插入return false;}
}

可是,发现一个问题,这样的写法是无法修改外部实参的,即无法链入搜索树中,所以我们要采用引用传参或二级指针传参,这样才能实质修改外部的变量。

root == nullptr 就将 key 链入树中,此时 root 为最后一个节点左或右子树的别名

 

3.3 递归实现 EraseR

递归删除的主题逻辑与上面大致相同。

步骤如下:

1.root == nullptr,则返回false,即未找到 key,删除失败

2.如果root->_key 小于 key,递归走右子树,

3.如果root->_key 大于 key,递归走左子树

4.最后就是 root->key == key,则开始删除节点。

bool _EraseR(Node* root, const K& key)
{if (root == nullptr){return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}//找到key,删除else{}
}

当已经找到key值,进行删除时

1.如果左子树为空,则让root指向其右子树。如图:

 2.如果右子树为空,则让root指向左子树,如图:

3.当左子树、右子树都不为空时,采用替换法删除,交换 key 值,然后删除被替换的节点。

 交换过后,我们要删除 key 节点此时要使用root->right再次调用 _EraseR,如果直接使用  _EraseR(key),则会删除失败,因为树的结构已经被破坏。

代码如下:

bool EraseR(const K& key)
{return _EraseR(root, key);
}
bool _EraseR(Node*& root, const K& key)
{if (root == nullptr){return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}//找到key,删除else{//情况1、2要记录待删除的节点。Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}//左右都有孩子else{Node* min = root->_right;while (min->_left){min = min->_left;}swap(root->_key, min->_key);//这里不能直接调用erase,交换后,树的结构已经破坏,显示找不到key值//return EraseR(key); return _EraseR(root->_right, key);}delete del;return true;}
}

四、二叉树搜索树的应用

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

比如: 给一个单词 word,判断该单词是否拼写正确,具体方法如下:

  • 以词库中所有单词集合中的每个单词作为key,构造一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

2.KV模型:每一个关键码 key ,都有与之对应的值 value,即<key,value>的键值对。该种方法式在现实生活中非常常见:

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word,chinese>就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现的次数就是<world,count>,就够成一种键值对。

4.1 key 模型

现在我们将二叉搜索树的K模型套入实际案例中,顺便练习编程能力。

将要拼写的单词插入到二叉搜索树中,用户开始拼写单词,如果用户输入的单词在词库中并拼写正确,则输出"拼写正确",否则输出"拼写错误"。代码如下:

4.2 key-value 模型

现在我们将二叉搜索树的 key-value 模型套入实际案例中,顺便练习编程能力。

我们现在创建一个字典,用户输入英文,程序打印出中文。

首先我们要创建 key-value 模型,然后将值插入,通过查找key,然后输出其value值。注意,如果是k-value模型,find的返回值就应为节点的指针。

我们看看代码是如何书写的,并且尝试运行一下:

 通过key-value模型我们还可以实现统计次数的程序。

例如我们往树中插入字符串,如果该字符串已存在,则++该字符串的计数。最后使用 Inorder 打印树中的元素,注意,要将Inorder中的输出语句带上value进行输出噢~

代码及结果如下:

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

在实现完功能之后,我们来对二叉搜索树进行性能分析。

问:搜索二叉树增删查的时间复杂度为多少?

答:最坏的情况下为 O(h)  —— h为高度。

为什么不是lgN,而是O(h) ,我们来看看如果树的形状是以下这几种情况的呢?

对有 n 个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度函数,即结点越深,比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况下:二叉搜索树为完全二叉树(或接近完全二叉树),其平均比较次数为 lgN.

最差情况下:二叉搜索树退化为单支树(或者类似单支),其平均比较次数为 N/2. 

所以说,二叉搜索树的效率在这种情况下跟O(N)几乎没有区别。其本质是不平衡的,所以后面我们要学习AVL树和红黑树来保持平衡。这样,搜索的效率就会极高。

六、二叉搜索树(key、key-value)代码

Key模型。

//节点的定义
template <class K>
class BSTreeNode
{
public: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;
public://C++11用法,作用:强制编译器生成默认的构造BSTree() = default;bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key > key)parent->_left = cur;elseparent->_right = cur;return true;}void Inorder(){_Inorder(_root);cout << endl;}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;}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->_left == nullptr){if (parent == nullptr){_root = _root->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr){if (parent == nullptr){_root = _root->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{Node* minparent = cur;Node* min = cur->_right;while (min->_left != nullptr){minparent = min;min = min->_left;}swap(cur->_key, min->_key);if (minparent->_left == min)minparent->_left = min->_right;elseminparent->_right = min->_right;delete min;}return true;}}return false;}bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}~BSTree(){_Destory(_root);}BSTree(const BSTree<K>& t){_root = _Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(t._root, _root);return *this;}private:Node* _Copy(Node* root){if (root == nullptr){return nullptr;}Node* copyRoot = new Node(root->_key);copyRoot->_left = _Copy(root->_left);copyRoot->_right = _Copy(root->_right);return copyRoot;}void _Destory(Node* root){if (root){_Destory(root->_left);_Destory(root->_right);delete root;}}bool _EraseR(Node*& root, const K& key){if (root == nullptr){return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}//找到key,删除else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}//左右都有孩子else{Node* min = root->_right;while (min->_left){min = min->_left;}swap(root->_key, min->_key);//这里不能直接调用erase,交换后,树的结构已经破坏,显示找不到key值return _EraseR(root->_right, key);}delete del;return true;}}//传引用bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{//已存在 key 不允许插入return false;}}bool _FindR(const Node* root, const K& key){if (root == nullptr)return false;if (root->_key < key){_FindR(root->_right, key);}else if (root->_key > key){_FindR(root->_left, key);}else{return true;}}void _Inorder(Node* root){if (root){_Inorder(root->_left);cout << root->_key << " ";_Inorder(root->_right);}}Node* _root = nullptr;
};

Key-Value模型,Find、Insert函数做出相应修改,并删除了部分函数

class BSTreeNode
{
public:BSTreeNode<K, Value>* _left;BSTreeNode<K, Value>* _right;K _key;Value _value;BSTreeNode(const K& key, const Value& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template <class K, class V>
class BSTree
{typedef BSTreeNode<K, V> Node;
public:bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key, value);if (parent->_key > key)parent->_left = cur;elseparent->_right = cur;return true;}void Inorder(){_Inorder(_root);cout << endl;}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;}private:void _Inorder(Node* root){if (root){_Inorder(root->_left);cout << root->_key << ":" << root->_value << endl;_Inorder(root->_right);}}Node* _root = nullptr;
};

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

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

相关文章

Linux开发工具

目录 一、yum工具 1.yum 背景知识 &#xff08;1&#xff09;商业生态 &#xff08;2&#xff09;开源生态 &#xff08;3&#xff09;软件生态本土化 2.yum 的基本使用 &#xff08;1&#xff09;查看软件包 &#xff08;2&#xff09;软件包名称构成 &#xff08;3&a…

高级架构师_Redis_第2章_数据类型与底层数据结构

高级架构师_Redis_第2章_数据类型与底层数据结构 文章目录高级架构师_Redis_第2章_数据类型与底层数据结构第二章&#xff1a;数据类型与底层数据结构本章学习目标&#xff1a;第一节&#xff1a;Redis 数据类型选择和应用场景1.1 Redis 的 Key 的设计1.2 String 字符串类型1.3…

SpringSecurity Oauth2实战 - 04 自定义AuthProvider实现登录认证

文章目录1. 搭建资源服务器1. Token存储配置类 TokenStoreAutoConfiguration2. 资源服务器配置类 ResourceServerAutoConfiguration3. 在META-INF/spring.factories文件下添加配置类2. 搭建授权服务器1. 密码加密配置类 PasswordEncodeConfig2. RestTemplateConfig3. 授权服务器…

SQL学习笔记(未完待续)

鉴于自己最近在做后端开发的工作时&#xff0c;发现自己的SQL能力实在太差&#xff0c;开始学习SQL语句基础&#xff0c;学习过程中在本博客进行笔记记录&#xff0c;课程参考&#xff1a;https://www.bilibili.com/video/BV1UE41147KC?p2 基本概念 DBMS: 数据库管理系统&am…

基于Python实现的文章整合搜索引擎网站(Scrapy+Django+MySQL)

目 录 摘 要… 1 1 概述… 6 2 技术选型… 6 2.1 Scrapy-Redis 分布式爬虫 … 6 2.1.1 Redis… 6 2.1.2 Scrapy… 7 2.2 MySQL 数据存储 … 8 2.3 Django 搭建搜索网站 … 8 2.4 ElasticSearch 搜索引擎 … 9 2.4.1 Elasticsearch-RTF… 9 2.4.2 Elasticsearch-head… 10 2.4.3…

Kotlin编程实战——集合(07)

一 概述 集合概述构造集合迭代器(Iterable<T>)区间与数列序列(Sequence<T>)集合操作概述集合转换集合过滤加减操作符分组(groupBy())取集合的一部分取单个元素集合排序集合聚合操作集合写操作List 相关操作Set 相关操作Map 相关操作 二 集合概述 set、list 、map…

【python】Numpy统计函数总结

文章目录函数列表相关系数直方图函数列表 最值amin, amax, nanmin, nanmax, 极差ptp分位数percentile∗^*∗ quantile∗^*∗,统计量中位数median∗^*∗&#xff1b;平均数mean∗^*∗&#xff1b;变化幅度var&#xff1b;加权平均average标准差std&#xff1b;协方差cov&#x…

运算放大器正反馈负反馈判别法

---------------------------------------------------------------------------------------------------------------- 反馈可分为负反馈和正反馈。前者使输出起到与输入相反的作用&#xff0c;使系统输出与系统目标的误差减小&#xff0c;系统趋于稳定&#xff1b;后者使输出…

浅谈java中的String

Java中的String类型不属于八大基本数据类型&#xff0c;而是一个引用数据类型&#xff0c;所以在定义一个String对象的时候如果不直接赋值给这个对象&#xff0c;它的默认值就是null。我们要怎么理解String类型的不可变&#xff0c;在JDK源码中String这个类的value方法被final关…

【C++】如何修改set的值

问题&#xff1a;尝试通过begin方法得到的迭代器去修改值&#xff0c;发现会报错。 set<string> st{"hello", "world", "good"}; set<string>::iterator it st.begin(); *it "test"; 原因&#xff1a;我们可以在源码里…

怎么搭建搜题接口api

怎么搭建搜题接口api 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点击…

RTSP协议学习Ubuntu环境准备

文章目录RTSP协议学习Ubuntu环境准备RTSP协议概述Ubuntu环境准备一、Ubuntu安装FFmpeg二、安装ZLMediaKit1、获取代码2、强烈推荐3、编译器3.1、编译器版本要求3.2、安装编译器4、cmake5、依赖库5.1、依赖库列表5.2、安装依赖库6、构建和编译项目7、运行8、测试三、测试推流测试…

【Tomcat】解决Tomcat服务器乱码问题

俩地方开展出现乱码的原因1、以startup.bat文件打开的服务器出现乱码2、在IDEA中运行Tomcat服务器出现乱码问题3、有关社区版IDEA如何开发JavaWeb项目出现乱码的原因 使用了错误的字符编码去解码字节流&#xff0c;所以出现乱码咱思维要清晰&#xff0c;就去找字符编码是否与其…

【TS04——接口的多态——泛型接口】

接口的多态&#xff0c;同一个方法&#xff0c;传入不同的参数&#xff0c;他所对应的操作不同成为多态【参数不同】或者可以理解为同一个方法&#xff0c;返回不同的结果&#xff0c;称之多态。 interface IfnPerson {run():voidrun(id:number):voidrun(id:number,name:strin…

【生日快乐】Node.js 实战 第1章 欢迎进入Node.js 的世界 1.3 安装Node

Node.js 实战 文章目录Node.js 实战第1章 欢迎进入Node.js 的世界1.3 安装Node第1章 欢迎进入Node.js 的世界 1.3 安装Node 安装Node的最简单的方法是使用其官网上的安装程序。可以用对应Mac或 Windows的安装程序安装最新的当前版。 官网安装包下载地址&#xff1a;https://…

Jenkins部署详细教程

Jenkins简介 Jenkins 是一个可扩展的持续集成引擎。是一个自成一体的开源自动化服务器, 可用于自动化与构建、测试、交付或部署软件相关的各种任务; Jenkins是一个高度可扩展的产品, 其功能可以通过安装插件来扩展。 在gitlab里可以完成源代码的管理&#xff0c;但是对于研发…

[ACTF2020 新生赛]Exec1命令注入

1.来看题目如下 得到一个ping的输入框&#xff0c;老样子先检查网页源码看有没有什么好东西&#xff0c;得到一个链接&#xff0c;我们来访问一下 发现也没什么有用处的信息&#xff0c;于是看到题目的标题之后联想到了命令注入&#xff0c; 那么是怎么判断使用命令注入的呢&am…

MyBatis初步了解

1.Mybatis简介 1.1原始jdbc操作&#xff08;查询数据&#xff09; 1.2原始jdbc操作&#xff08;插入数据&#xff09; 1.3 原始jdbc操作的分析 原始jdbc开发存在的问题如下&#xff1a; ①数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能 ②sql 语句在代码中硬编…

深入理解Java虚拟机:Java运行内存结构

本篇内容包括&#xff1a;JAVA 运行内存结构&#xff0c;即 程序计数器、Java 虚拟机栈、本地方法栈 、Java堆、方法区、运行时常量池 以及 直接内存等相关内容&#xff01; 一、JAVA 运行内存结构 Jvm 执行 Java 程序时&#xff0c;会把它所管理的内存划分为若干个不同的数据…

软件设计与体系——创建型模式

如果有兴趣了解更多相关内容&#xff0c;欢迎来我的个人网站看看&#xff1a;瞳孔的个人空间 创建型模式&#xff1a; 创建型模式抽象了实例化过程帮助系统独立于如何创建、组合和表示对象 一个类创建型模式使用继承改变被实例化的类类创建型模式使用继承改变被实例化的类对象…