[C++进阶数据结构]二叉搜索树

多态讲完了,我们来讲点轻松的(也许)。

我们之前讲过二叉树,而二叉树中,又有一种特殊的树称之为二叉搜索树。

一、二叉搜索树的概念

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

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

例如下图,左边的是一颗二叉搜索树,右边不是:

为什么这样的树称作二叉搜索树呢?因为这树天生就十分适合搜索。我们可以这么想使用左边的树,如果我们想找到22的话,首先22比25小,所以一定在左子树,而22大于20,所以一定在右子树。然后我们就可以找到了。我们可以发现,这种树的效率天生就很高,这棵树最多找高度次。也就是说最多找N次(注意不是logN次,因为我们可能碰到下图中右边的树),下图中的两棵树都是二叉搜素树。也就是说时间复杂度为O(N)

普通的二叉搜索树的时间复杂度为O(N),但是我们可以对其进行一些特殊变换,使之成为AVL树,红黑树之类的(之后我们再讲)。这样时间复杂度就可以降为了O(logN)了。

这个树还有另一个名字是二叉排序树,这是因为当我们对这棵树进行中序遍历的时候,结果是升序的。

二、二叉搜索树的实现

1.二叉树的结点定义

对于二叉树的结点,我们类似于list的结点定义一样,使用一个struct类。

template<class K>
struct BSTreeNode
{BSTreeNode(const K& val = K()):_key(val),_left(nullptr),_right(nullptr){}K _key;BSTreeNode<K>* _left;BSTreeNode<K>* _right;
};

2.二叉搜索树的结构

为了方便我们后续的结点定义,我们将结点给typedef一下。然后我们的成员变量其实也就这一个根节点。只要有根节点就可以构建出二叉搜索树了。

private:typedef BSTreeNode<K> Node;Node* _root;

3.二叉搜索树的构造函数

public:BSTree():_root(nullptr){}

如上代码所示,对于构造函数其实是比较简答的,我们只需要将二叉树的根结点初始化为空即可

4.二叉搜索树的插入


对于插入接口,我们需要考虑的情况有如下两种:

  1. 二叉树为空的时候,直接new一个结点插入上去即可
  2. 如果二叉树不为空,那么我们就需要进行比较

例如上面的例子,我们此时插入一个16

16小于25,向左走。

16小于20,向左走。

16大于10,向右走,并插入空

注意:如果恰好等于当前结点的值,那么我们可以认为插入失败了,因为此时已经不满足二叉搜索树的定义了。

代码实现:

我们现在想的方法是对二叉树进行迭代,迭代到空new一个新节点插入,但是如果我们只是对二叉树进行迭代最终我们会发现如此迭代下去,最终的当前结点一定会为空指针,这并非我们所期望的。我们肯定是还需要它的父节点的,这样才能让我目标值插入进去。所以我们还需要一个值来记录当前所迭代位置的父节点。即我们下面写的parent。

有了parent以后,我们现在就是new一个新的结点,然后直接parent链接起来。不过此时又分为两种情形,是左孩子还是右孩子,其实并不好确定。于是我们可以使用parent当前的值与目标值进行比对从而判断出是插入左孩子还是右孩子。

public:bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}else{Node* parent = _root;Node* cur = _root;while (cur != nullptr){if (cur->_key == key){return false;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{parent = cur;cur = cur->_right;}}cur = new Node(key);if (parent->_key > key){parent->_left = cur;}else if (parent->_key < key){parent->_right = cur;}return true;}}

5.二叉搜索树的中序遍历


插入了以后,我们可以使用中序遍历对其进行输出,由于中序遍历就是排序后的情形,所以我们可以很方便的看到我们当前二叉树的结果。

对于排序,我们最好写一个子函数来实现,因为没有子函数的话,参数不好进行控制,会使得代码十分繁琐。

如下代码所示,对于中序遍历我们其实还是比较容易看懂的。无非就是先左子树,然后根,然后右子树即可。我们最好将子函数放在私有的部分中,因为在类外我们并不需要直接使用这个函数

public:void InOrder(){_InOrder(_root);}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}

6.二叉搜索树的查找


对于查找的逻辑,是非常简单的,它与插入也是比较相似的,当我们当前结点的值小于目标的时候,迭代到右子树,当大于目标的时候,迭代到左子树。当相等的时候返回true即可。但若当cur都为nullptr了的时候还没有找到,那就是没有找到,返回false即可

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

7.二叉搜索树的删除

对于二叉搜索树的删除操作,其实是比较困难的。

首先查找元素是否在二叉搜索树中,如果不存在就返回false

如果存在,那么此时要删除的节点可能分为以下四种情况:

  1. 要删除的节点无子节点
  2. 要删除的节点只有左子节点
  3. 要删除的节点只有右子节点
  4. 要删除的节点有左、右子节点

第一种情况是最好处理的,释放节点,直接把父节点指向改为空。

第二种情况,要删除的节点只有左子节点,我们还是用上面的例子,此时18就是一个只有左子节点的节点

如果我们要删除2,只需要将2的父节点和2的左子节点链接,然后直接删除2即可。

第三种情况,要删除的节点只有右子节点,此时13就是一个只有右子节点的节点

如果我们要删除13,只需要将13的父节点和13的右子节点链接,然后直接删除13即可

总结,第二种和第三种情况的删除方式其实也适用于第一种情况,因为无子节点,也就是左右都为空,此时父节点链接的是一个空指针,并删除目标节点即可。

第四种:要删除的节点有左、右节点,此时23符合要求

我们的目标是,在23的子树中寻找一个值,将23与这个值交换。这种方法叫做替换法。

要寻找的值必须满足交换后二叉搜索树的性质不变,所以我们需要在23的左子树找出一个最大值,或者在23的右子树找出一个最小值。

前面提到过对二叉搜索树中序遍历可以得到一个有序的序列,所以23的子树的最大值,也就是在左子树中做一次中序遍历下的最后一个数,也就是21。简单来说,我们只需要从23向左走一步,然后一直向右走,就可以找到左子树的最大值了

而23的右子树的最小值,也就是在右子树中做一次中序遍历下的第一个数,也就是26。和上面类似的,我们只需要从26向右走一步,然后一直向左走,就可以找到右子树的最小值了。

因此,我们可以将23和21替换,或者将23和26替换

这里就只展示23和21替换的情况,当学会规律后,另一种情况也就学会了。

替换后,还需要把23的父节点和23的子节点链接,这里又有两种情况需要区分,否则导致错误:

  1. 替换后23的父节点位于23原先的位置
  2. 替换后23的父节点不位于23原先的位置

可以看出,在这种情况下23的父节点位于23原先的位置,那么就要将此时23的左子节点赋值给父节点的left,然后就可以直接删除23了

我们改造一下这颗二叉搜索树.使父节点不位于原先的位置(另一种情况)

假设我们要删去12(改造后12的左子树的最大值为11)

此时,就需要将12的左子节点赋值给父节点的right,然后直接删除12。

这里是一个易错点,需要注意情况的判断。以上是对二叉搜索树的操作部分。

因此我们引出了两种写法,

以下是先考虑parent与cur这两代人之间的关系所写的代码

public:bool Erase(const K& key){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{if (parent == nullptr){if (cur->_left == nullptr){_root = cur->_right;delete cur;return true;}else if (cur->_right == nullptr){_root = cur->_left;delete cur;return true;}else{Node* leftMaxParent = cur;Node* leftMax = cur->_left;if (leftMax->_right == nullptr){leftMax->_right = cur->_right;delete cur;_root = leftMax;return true;}while (leftMax->_right){leftMaxParent = leftMax;leftMax = leftMax->_right;}std::swap(leftMax->_key, cur->_key);leftMaxParent->_right = leftMax->_left;delete leftMax;leftMax = nullptr;return true;}}if (parent->_left == cur){if (cur->_left == nullptr){parent->_left = cur->_right;delete cur;return true;}else if (cur->_right == nullptr){parent->_left = cur->_left;delete cur;return true;}else {Node* leftMaxParent = cur;Node* leftMax = cur->_left;if (leftMax->_right == nullptr){leftMax->_right = cur->_right;delete cur;parent->_left = leftMax;return true;}while (leftMax->_right){leftMaxParent = leftMax;leftMax = leftMax->_right;}std::swap(leftMax->_key, cur->_key);leftMaxParent->_right = leftMax->_left;delete leftMax;leftMax = nullptr;return true;}}else{if (cur->_left == nullptr){parent->_right = cur->_right;delete cur;return true;}else if (cur->_right == nullptr){parent->_right = cur->_left;delete cur;return true;}else{Node* leftMaxParent = cur;Node* leftMax = cur->_left;if (leftMax->_right == nullptr){leftMax->_right = cur->_right;delete cur;parent->_right = leftMax;return true;}while (leftMax->_right){leftMaxParent = leftMax;leftMax = leftMax->_right;}std::swap(leftMax->_key, cur->_key);leftMaxParent->_right = leftMax->_left;delete leftMax;leftMax = nullptr;return true;}}}}return false;}

以下是根据先考虑cur与其孩子之间的关系所写的代码

public:bool Erase(const K& key){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{if (cur->_left == nullptr){if (parent == nullptr){_root = cur->_right;}else if (parent->_left == cur){parent->_left = cur->_right;}else if (parent->_right == cur){parent->_right = cur->_right;}}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else if (parent->_left == cur){parent->_left = cur->_left;}else if (parent->_right = cur){parent->_right = cur->_left;}}else{Node* leftMax = cur->_left;Node* leftMaxParent = cur;while (leftMax->_right){leftMaxParent = leftMax;leftMax = leftMax->_right;}std::swap(cur->_key, leftMax->_key);if (leftMaxParent->_left == leftMax){leftMaxParent->_left = leftMax->_left;}else{leftMaxParent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}}

这两种方法都可以完成这个接口,但是代码量有显著的差异。这里推荐先考虑cur和其后代之间的关系。因为替代法这个出现于cur与其后代之间的关系之中。

8.二叉搜索树的查找(递归)

前面,都是讲的非递归,此处为补充(能用迭代还是用迭代去找)

二叉树其实本身还是比较适合递归的。为了可以控制参数,我们要写一个子函数去解决,当根节点为空的时候,直接返回false即可,当前的结点小于要查找的结点,那么去右子树找,当大于时候,去左子树找即可。

public:bool FindR(const K& key){return _FindR(_root, key);}
private:bool _FindR(Node* root, const K& key){if (root == nullptr){return false;}if (root->_key == key){return true;}else if (root->_key > key){return _FindR(root->_left, key);}else{return  _FindR(root->_right, key);}}

9.二叉搜索树的插入(递归)

大体思路同上,也是补充而已

public:bool InsertR(const K& key){return _InsertR(_root, key);}
private: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{return false;}}

10.二叉搜索树的删除(递归)

这个写的挺妙的

public:	bool EraseR(const K& key){return _EraseR(_root, key);}
private: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);}else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}std::swap(leftMax->_key, root->_key);return _EraseR(root->_left, key);}delete del;return true;}}

11.二叉搜索树的销毁

拉欧i据 直接按照后序的方式进行销毁即可。

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

这里最好传引用,因为这样可以置空root指针

12.拷贝构造函数

拷贝构造函数,我们需要实现一个深拷贝,和之前的二叉树基本一样,我们讲deque时也做过了。

这里我们使用先序的方式进行构造:

public:BSTree(const BSTree<K>& t){_root = Copy(t._root);}
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;}

13.运算符重载

这里主要用的还是赋值运算符重载吧,

现代写法启动:

public:BSTree<K>& operator=(BSTree<K> t){std::swap(_root, t._root);return *this;}​

14.二叉搜索树的实现

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;
public:bool insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* prev = nullptr;Node* cur = _root;while (cur){prev = cur;if (key > cur->_key)cur = cur->_right;else if (key < cur->_key)cur = cur->_left;elsereturn false;}cur = new Node(key);if (key > prev->_key)prev->_right = cur;elseprev->_left = cur;return true;}bool insertR(const K& key) //递归版本{return _insertR(_root, key);}bool find(const K& key){Node* cur = _root;while (cur){if (key > cur->_key)cur = cur->_right;else if (key < cur->_key)cur = cur->_left;elsereturn true;}return false;}bool findR(const K& key) //递归版本{return _findR(_root, key);}bool erase(const K& key) //替换法:把目标替换成左子树的最大值或右子树的最小值{Node* parent = nullptr;Node* cur = _root;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{if (cur->_left == nullptr) //左子树为空{if (cur == _root)_root = cur->_right;else{if (cur == parent->_right)parent->_right = cur->_right;elseparent->_left = cur->_right;}}else if(cur->_right == nullptr) //右子树为空{if (cur == _root)_root = cur->_left;else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}}else //左右子树都不为空{parent = cur;Node* leftmid = cur->_left;while (leftmid->_right){parent = leftmid;leftmid = leftmid->_right;}swap(cur->_key, leftmid->_key);if (parent == cur)parent->_left = leftmid->_left;elseparent->_right = leftmid->_left;cur = leftmid;}delete cur;return true;}}return false;}bool eraseR(const K& key) //递归版本{return _eraseR(_root, key);}void InOrder(){_InOrder(_root);cout << endl;}BSTree() = default;BSTree(const BSTree<K>& t){_root = copy(t._root);}~BSTree(){Destroy(_root);}private: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;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}bool _findR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key == key)return true;if (key > root->_key)return _findR(root->_right, key);elsereturn _findR(root->_left, key);}bool _insertR(Node*& root, const K& key) //参数中指针一定要用引用,不然链接不上{if (root == nullptr){root = new Node(key);return true;}if (key > root->_key)return _insertR(root->_right, key);else if (key < root->_key)return _insertR(root->_left, key);elsereturn false;}bool _eraseR(Node*& root, const K& key){if (root == nullptr)return false;if (key > root->_key)return _eraseR(root->_right, key);else if (key < root->_key)return _eraseR(root->_left, key);else{if (root->_left == nullptr){Node* tmp = root;root = root->_right; //由于引用,root是父节点子树的别名,所以可以直接修改delete tmp;}else if (root->_right == nullptr){Node* tmp = root;root = root->_left;delete tmp;}else{Node* leftmid = root->_left;while (leftmid->_right)leftmid = leftmid->_right;swap(root->_key, leftmid->_key);return _eraseR(root->_left, key);}return true;}}
private:Node* _root = nullptr;
};

三、二叉搜索树的应用

1)key模型

即只有key作为关键码,只需要在二叉搜索树中存储一个key即可。例如我们将业主的信息作为key,将整个小区所有业主的key存储进二叉搜索树中,如果此时要查询一个人是否是小区的业主,只需要将他的key在二叉搜索树中查找一下就可以知道这个人是否是业主了。后面要学习的set,就是一个key模型

2)key/value模型

每一个关键码key,都有与之对应的值value,也就是要在二叉搜索树中存储<key, value>的键值对。例如字典就是一个key/value模型,英文单词作为key,中文作为value,通过key就可以查找到value。又例如哈希表或者类似的用于统计某个物品出现次数的结构,通过查找该物品就可以快速得到其出现次数,这也是一个key/value模型。后面要学习的map,就是一个key/value模型我们可以将二叉搜索树改造为key/value的结构,让节点中多存储一个value,再进行一些修改即可

template<class K, class V>
struct BSTreeNode
{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _val;BSTreeNode(const K& key, const V& val):_left(nullptr), _right(nullptr), _key(key), _val(val){}
};template<class K, class V>
class BSTree
{typedef BSTreeNode<K, V> Node;
public:bool insert(const K& key, const V& val){if (_root == nullptr){_root = new Node(key, val);return true;}Node* prev = nullptr;Node* cur = _root;while (cur){prev = cur;if (key > cur->_key)cur = cur->_right;else if (key < cur->_key)cur = cur->_left;elsereturn false;}cur = new Node(key, val);if (key > prev->_key)prev->_right = cur;elseprev->_left = cur;return true;}Node* find(const K& key){Node* cur = _root;while (cur){if (key > cur->_key)cur = cur->_right;else if (key < cur->_key)cur = cur->_left;elsereturn cur;}return nullptr;}bool erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{if (cur->_left == nullptr){if (cur == _root)_root = cur->_right;else{if (cur == parent->_right)parent->_right = cur->_right;elseparent->_left = cur->_right;}}else if (cur->_right == nullptr){if (cur == _root)_root = cur->_left;else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}}else{parent = cur;Node* leftmid = cur->_left;while (leftmid->_right){parent = leftmid;leftmid = leftmid->_right;}swap(cur->_key, leftmid->_key);if (parent == cur)parent->_left = leftmid->_left;elseparent->_right = leftmid->_left;cur = leftmid;}delete cur;return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " : " << root->_val << endl;_InOrder(root->_right);}private:Node* _root = nullptr;
};

本篇文章到此结束,如有错误感谢指正。

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

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

相关文章

中电信翼康济世数据中台基于Apache SeaTunnel构建数据集成平台经验分享

作者 | 中电信翼康工程师 代来 编辑 | Debra Chen 一. 引言 Apache SeaTunnel作为一个高性能、易用的数据集成框架&#xff0c;是快速落地数据集成平台的基石。本文将从数据中台战略背景、数据集成平台技术选型、降低Apache SeaTunnel使用门槛及未来展望几个方面&#xff0c…

【环绕字符串中唯一的子字符串】python刷题记录

R4-字符串 动态规划 class Solution:def findSubstringInWraproundString(self, s: str) -> int:dp[0]*26num1#dp初始化dp[ord(s[0])-ord(a)]1for c1,c2 in pairwise(s):if not (ord(c2)-ord(c1)-1)%26:num1else:num1dp[id]max(dp[id : ord(c2)-ord(a)],num)return sum(dp)p…

调用azure的npm实现outlook_api模拟查看邮件、发送邮件(实现web版接受outlook邮件第一步)

文章目录 ⭐前言⭐注册azure应用&#x1f496;添加权限 ⭐调用npm 实现收发邮件&#x1f496;安装依赖&#x1f496;创建appSettings.js 放置密钥&#x1f496;创建graphHelper.js封装功能&#x1f496;主文件index.js 对外暴露&#x1f496;效果 ⭐结束 ⭐前言 大家好&#x…

Flutter GPU 是什么?为什么它对 Flutter 有跨时代的意义?

Flutter 3.24 版本引入了 Flutter GPU 概念的新底层图形 API flutter_gpu &#xff0c;还有 flutter_scene 的 3D 渲染支持库&#xff0c;它们目前都是预览阶段&#xff0c;只能在 main channel 上体验&#xff0c;并且依赖 Impeller 的实现。 Flutter GPU 是 Flutter 内置的底…

2024最新Mysql锁机制与优化实践以及MVCC底层原理剖析

锁机制详解 锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中&#xff0c;除了传统的计算资源&#xff08;如CPU、RAM、I/O等&#xff09;的争用以外&#xff0c;数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解…

3Done学习笔记

一、基本操作 1、旋转视角 使用左下角立方体选择&#xff1b; 右键可以拖动视角&#xff1b; 中间滑轮按住拖动整个舞台界面。 2、平移和旋转 右键选择移动&#xff0c;有两种方式。 第一种选择起始点&#xff0c;按照起始点位置移动到终止点。第二种直接根据轮盘旋转或…

【docker】docker容器部署常用服务

1、容器部署nginx&#xff0c;并且新增一个页面 docker run -d -p 81:80 --name nginx2 nginx docker exec -it nginx2 /bin/bashcd /usr/share/nginx/html/ echo "hello world">>hello.html2、容器部署redis&#xff0c;成功部署后向redis中添加一条数据 do…

【C/C++笔记】:易错难点3 (二叉树)

选择题 &#x1f308;eg1 一棵有15个节点的完全二叉树和一棵同样有15个节点的普通二叉树&#xff0c;叶子节点的个数最多会差多少个&#xff08;&#xff09;&#xff1f; 正确答案&#xff1a; C A. 3 B. 5 C. 7 D. 9 解析&#xff1a;普通二叉树的叶子节…

WPF学习笔记

WPF WPF&#xff08;Windows Presentation Foundation&#xff0c;Windows呈现基础&#xff09;是微软推出的基于Windows 的用户界面框架&#xff0c;属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架&#xff0c;真正做到了分离界面设计人员与开发人员的…

C语言----计算开机时间

计算开机时间 实例说明 编程实现计算开机时间&#xff0c;要求在每次开始计算开机时间时都能接着上次记录的结果向下记录。 实现过程&#xff1a; 1. 在TC中创建一个C文件。 2. 引用头文件&#xff0c;代码如下: #include <stdio.h> 3. 定义结构体time&#xff0c;用来…

如何在Chrome、Edge、360、Firefox等浏览器查看网站SSL证书信息?

在如今的网络环境中&#xff0c;保障网络安全、数据安全尤其重要&#xff0c;市面上大部分网站都部署了SSL证书以实现HTTPS加密保护数据传输安全以及验证网站身份&#xff0c;确保网站安全可信。那么如何查看网站的SSL证书信息&#xff1f;接下来&#xff0c;我们将详细介绍如何…

【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式

文章目录 webView使用步骤示例 HttpURLConnection使用步骤示例GET请求POST请求 okHttp使用步骤1. 添加依赖2. 创建OkHttpClient实例3. 创建Request对象构建请求4. 发送请求5. 获取响应 Pull解析方式1. 准备XML数据2. 创建数据类3. 使用Pull解析器解析XML webView WebView 是 An…

【Nacos无压力源码领读】(三) Nacos 配置中心与热更新原理详解 敢说全网最细

本文将从 Nacos 配置中心的基本使用入手, 详细介绍 Nacos 客户端发布配置, 拉取配置, 订阅配置的过程以及服务器对应的处理过程; 配置订阅以及热更新原理相关的部分, 我看了主流的博客网站, 绝对没有比这更详细的讲解; 如果在阅读过程中对文中提到的 SpringBoot 启动过程以及…

Milvus与Zilliz Cloud:向量数据库高可用性的双重飞跃

向量数据库高可用性的重要性及其在现代数据分析中的关键作用 在数据爆炸式增长的今天,企业对于高效、准确地处理和分析大规模数据集的需求日益迫切。尤其是在人工智能、机器学习、图像识别、自然语言处理等领域,向量数据库因其对高维数据的高效存储与检索能力,成为了不可或…

Elasticsearch未授权访问漏洞

步骤一:使用以下Fofa语法进行Elasticsearch产品搜索.. fofa语法"Elasticsearch" && port"9200" 步骤二:存在未授权访问则直接进入到信息页面...不需要输入用户密码登陆. http://localhost:9200/_plugin/head/web管理界面 http://localhost:9200/…

【JavaEE】线程池

目录 前言 什么是线程池 线程池的优点 ThreadPollExecutor中的构造方法 corePoolSize && maximumPoolSize keepAliveTime && unit workQueue threadFactory 如何在java中使用线程池 1.创建线程池对象 2.调用submit添加任务 3.调用shutdown关闭线程池…

【Python】requests的response.text 和 urllib.request 的 response.read()的区别

刚写代码的时候&#xff0c;我经常会把requests 和 urllib下的request 包搞混&#xff0c;这两个请求响应的方法看起来很相似&#xff0c;但是写获取的方法是不一样的。 前者requests 是用response.text 来获取源码&#xff0c;而 urllib.request是用 response.read() 来获取h…

数学建模--智能算法之免疫算法

目录 基本原理 应用实例 代码示例 总结 免疫算法在免疫系统研究中的应用和进展是什么&#xff1f; 如何量化评估免疫算法在不同优化问题中的性能和效率&#xff1f; 免疫算法与其他智能优化算法&#xff08;如遗传算法、粒子群优化&#xff09;相比有哪些独特优势和局限性…

【C++】C++11的新特性 — 线程库 ,原子操作 , 条件变量

勇敢就是接受发生在你身上的事&#xff0c;并把它尽力做到最好。 -- 约翰・欧文 -- C11的新特性 1 线程1.1 线程概念1.2 C中的线程1.3 线程并行1.4 锁 2 原子操作3 条件变量Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下…

编译和汇编的区别

一、编译 编译是将高级语言&#xff08;如C、C、Java等&#xff09;编写的源代码转换成计算机可以直接执行的低级语言&#xff08;通常是机器语言或汇编语言&#xff09;的过程 编译 —— 将人类可读的源代码转换为计算机可执行的指令集 编译过程 通常包括词法分析、语法分…