数据结构~二叉搜索树

文章目录

    • 一、二叉树搜索的概念
    • 二、二叉树搜索的结构
      • 二叉树搜索的性能分析
      • 二叉树搜索的插入
      • 二叉树搜索的查找
      • 二叉树搜索的删除
    • 三、二叉搜索树key和key/value使用场景
    • 四、二叉树搜索的练习
      • 将二叉搜索树就地转化为已排序的双向循环链表
      • 从前序与中序遍历序列构造二叉树
      • 二叉树的前序遍历(非递归)
    • 五、完整代码
      • 二叉树搜索的实现代码
      • key/value二叉树搜索代码实现
    • 六、总结

一、二叉树搜索的概念

二叉树搜索又称二叉排序树(Binary Search Tree,BST),它或者是一棵空树,或者是具有以下性质的⼆叉树:

  • 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值
  • 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值
  • 它的左右子树也分别为二叉搜索树
  • 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值
    在这里插入图片描述

二、二叉树搜索的结构

二叉树搜索的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全⼆叉树),其高度为:O(log2N)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其高度为:O(N/2)
所以综合而言⼆叉搜索树增删查改时间复杂度为:O(N)
那么这样的效率显然是无法满足需求的,后续讲解⼆叉搜索树的变形,平衡二叉搜索树AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。另外需要说明的是,二分查找也可以实现O(logN) 级别的查找效率,但是二分查找有两大缺陷:

  1. 需要存储在支持下标随机访问的结构中,并且有序。
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。这里也就体现出了平衡⼆叉搜索树的价值。
    在这里插入图片描述

二叉树搜索的插入

插入的具体过程如下:

  1. 树为空,则直接新增结点,赋值给root指针
  2. 树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
  3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要⼀会往右走,⼀会往左走)
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二叉树搜索的查找

  1. 从根开始比较,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。
  2. 最多查找高度次,走到到空,还没找到,这个值不存在。
  3. 如果不支持插入相等的值,找到x即可返回
  4. 如果支持插入相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。如下图,查找3,要找到1的右孩子的那个3返回
  5. 在这里插入图片描述

二叉树搜索的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回false。
如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

  1. 要删除结点N左右孩子均为空
  2. 要删除的结点N左孩子位空,右孩子结点不为空
  3. 要删除的结点N右孩子位空,左孩子结点不为空
  4. 要删除的结点N左右孩子结点均不为空

对应以上四种情况的解决方案:

  1. 把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的)
  2. 把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点
  3. 把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结
  4. 无法直接删除N结点,因为N的两个孩子无处安放,只能用替换法删除。找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)替代N因为这两个结点中任意⼀个,放到N的位置,都满足⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

三、二叉搜索树key和key/value使用场景

key搜索场景:
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。
场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,车辆进入时扫描车牌在不在系统中,在则抬杆,不在则提示非本小区车辆,无法进入。
场景2:检查⼀篇英文章章单词拼写是否正确,将词库中所有单词放入⼆叉搜索树,读取文章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提示。
在这里插入图片描述
key/value搜索场景:
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的二叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树结构了,可以修改value。
场景1:简单中英互译字典,树的结构中(结点)存储key(英文)和vlaue(中文),搜索时输入英文,则同时查找到了英文对应的中文。
场景2:商场无人值守车库,入口进场时扫车牌,记录车牌和入场时间,出口离场时,扫描车牌,查找入场时间,用当前时间-入场时间计算出停车时长,计算出停车费用,缴费后抬杆,车辆离场。
场景3:统计⼀篇文章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。
在这里插入图片描述

四、二叉树搜索的练习

将二叉搜索树就地转化为已排序的双向循环链表

将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表 。
对于双向循环列表,你可以将左右孩子指针作为双向循环链表的前驱和后继指针,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
特别地,我们希望可以 就地 完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中最小元素的指针。
在这里插入图片描述

思想:

  • 搜索二叉树走中序是有序的,本题目要求原地修改,也就是不能创建新的结点。
  • 思路1:中序遍历搜索⼆叉树,遍历顺序是有序的,将⼆叉树的结点指针放到⼀个vector中,再把前后结点的链接关系进⾏修改。这个思路最简单,但是需要消耗O(N)的空间复杂度。
  • 思路2:依旧中序遍历搜索二叉树,遍历顺序是有序的,遍历过程中修改左指针为前驱和右指针为后继指针。记录⼀个cur和prev,cur为当前中序遍历到的结点,prev为上⼀个中序遍历的结点,cur->left指向prev,cur->right⽆法指向中序下⼀个,因为不知道中序下⼀个是谁,但是prev->right指向cur;也就是说每个结点的左是在中遍历到当前结点时修改指向前驱的,但是当前结点的右,是在遍历到下⼀个结点时,修改指向后继的。

代码实现

class Solution {
public:void InOrderConvert(Node* cur, Node*& prev){if (cur == nullptr)return;// 中序遍历InOrderConvert(cur->left, prev);// 当前结点的左,指向前⼀个结点cur->left = prev;// 前⼀个结点的右,指向当前结点if (prev)prev->right = cur;prev = cur;InOrderConvert(cur->right, prev);}Node* treeToDoublyList(Node* root) {if (root == nullptr)return nullptr;Node* prev = nullptr;InOrderConvert(root, prev);// 从根开始往左⾛,找到第⼀个结点Node* head = root;while (head->left){head = head->left;}// head为第⼀个结点,prev是最后⼀个结点// 题⽬要求为循环链表,进⾏⼀些链接head->left = prev;prev->right = head;return head;}
};

从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
在这里插入图片描述
思路:
先序遍历的顺序是根节点、左子树、右子树。这意味着先序遍历的第一个元素就是根节点的值。
中序遍历的顺序是左子树、根节点、右子树。通过在中序遍历中找到根节点的位置,可以确定左子树和右子树的元素范围。
可以根据先序遍历和中序遍历构建出二叉树。这个过程利用了先序遍历确定根节点和中序遍历划分左右子树的特点,通过递归的方式逐步构建出整个二叉

class Solution {
public:TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int&prei, int inbegin, int inend) {if (inbegin > inend)return nullptr;// 前序确定根TreeNode* root = new TreeNode(preorder[prei++]);// 根分割中序左右⼦区间int rooti = inbegin;while (rooti <= inend){if (inorder[rooti] == root->val)break;elserooti++;}// 递归左右⼦区间,递归构建左右⼦树// [inbegin, rooti-1] rooti [rooti+1, inend]root->left = _buildTree(preorder, inorder, prei, inbegin, rooti - 1);root->right = _buildTree(preorder, inorder, prei, rooti + 1, inend);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int i = 0;TreeNode* root = _buildTree(preorder, inorder, i, 0, inorder.size() - 1);return root;}
};

二叉树的前序遍历(非递归)

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
在这里插入图片描述
在这里插入图片描述
非递归迭代实现思想:
要迭代非递归实现⼆叉树前序遍历,首先还是要借助递归的类似的思想,只是需要把结点存在栈中,方便类似递归回退时取父路径结点。跟这里不同的是,这里把⼀棵⼆叉树分为两个部分:

  1. 先访问左路结点
  2. 再访问左路结点的右子树
    这里访问右子树要以循环从栈依次取出这些结点,循环子问题的思想访问左路结点的右子树。中序和后序跟前序的思路完全⼀致,只是前序先访问根还是后访问根的问题。后序稍微麻烦⼀些,因为后序遍历的顺序是左子树右子树根,当取到左路结点的右子树时,需要想办法标记判断右子树是否访问过了,如果访问过了,就直接访问根,如果访问过需要先访问右子树
class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> s;vector<int> v;TreeNode* cur = root;while (cur || !s.empty()){// 访问⼀颗树的开始// 1、访问左路结点,左路结点⼊栈while (cur){v.push_back(cur->val);s.push(cur);cur = cur->left;}// 2、从栈中依次访问左路结点的右⼦树TreeNode* top = s.top();s.pop();// 循环⼦问题⽅式访问左路结点的右⼦树 --cur = top->right;}return v;}
};
class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {stack<TreeNode*> st;TreeNode* cur = root;vector<int> v;while (cur || !st.empty()){// 访问⼀颗树的开始// 1、左路结点⼊栈while (cur){st.push(cur);cur = cur->left;}// 访问问左路结点 和 左路结点的右⼦树TreeNode* top = st.top();st.pop();v.push_back(top->val);// 循环⼦问题⽅式访问右⼦树cur = top->right;}return v;}
};
class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {TreeNode* cur = root;stack<TreeNode*> s;vector<int> v;TreeNode* prev = nullptr;while (cur || !s.empty()){// 1、访问⼀颗树的开始while (cur){s.push(cur);cur = cur->left;}TreeNode* top = s.top();// top结点的右为空 或者 上⼀个访问结点等于他的右孩⼦// 那么说明(空)不⽤访问 或者 (不为空)右⼦树已经访问过了// 那么说明当前结点左右⼦树都访问过了,可以访问当前结点了if (top->right == nullptr || top->right == prev){s.pop();v.push_back(top->val);prev = top;}else{// 右⼦树不为空,且没有访问,循环⼦问题⽅式右⼦树cur = top->right;}}return v;}
};

五、完整代码

二叉树搜索的实现代码

// Binary Search Tree
template<class K>
class BSTree
{typedef BSTNode<K> Node;
public: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->_right = cur;}else{parent->_left = cur;}return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}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->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{// 0-1个孩⼦的情况 // 删除情况1 2 3均可以直接删除,改变⽗亲对应孩⼦指针指向即可if (cur->_left == nullptr){if (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur -> _right;elseparent->_right = cur -> _right;}delete cur;return true;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (parent->_left == cur)parent->_left = cur -> _left;elseparent->_right = cur -> _left;}delete cur;return true;}else{// 2个孩⼦的情况 // 删除情况4,替换法删除 // 假设这⾥我们取右⼦树的最⼩结点作为替代结点去删除// 这⾥尤其要注意右⼦树的根就是最⼩情况的情况的处理,对应课件图中删除8的情况// ⼀定要把cur给rightMinP,否会报错。 Node* rightMinP = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinP = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinP->_left == rightMin)rightMinP->_left = rightMin -> _right;elserightMinP->_right = rightMin -> _right;delete rightMin;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 << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};

key/value二叉树搜索代码实现

template<class K, class V>
struct BSTNode
{// pair<K, V> _kv;K _key;V _value;BSTNode<K, V>* _left;BSTNode<K, V>* _right;BSTNode(const K& key, const V& value):_key(key), _value(value), _left(nullptr), _right(nullptr){}
};
template<class K, class V>
class BSTree
{typedef BSTNode<K, V> Node;
public:BSTree() = default;BSTree(const BSTree<K, V>& t){_root = Copy(t._root);}BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);_root = nullptr;}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->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Erase(const K& key){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{if (cur->_left == nullptr){if (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur -> _right;elseparent->_right = cur -> _right;}delete cur;return true;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (parent->_left == cur)parent->_left = cur -> _left;elseparent->_right = cur -> _left;}delete cur;return true;}else{Node* rightMinP = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinP = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinP->_left == rightMin)rightMinP->_left = rightMin -> _right;elserightMinP->_right = rightMin -> _right;delete rightMin;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->_value << endl;_InOrder(root->_right);}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key, root->_value);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}
private:Node* _root = nullptr;
};
void test1()
{BSTree<string, string> dict;//BSTree<string, string> copy = dict;dict.Insert("left", "左边");dict.Insert("right", "右边");dict.Insert("insert", "插⼊");dict.Insert("string", "字符串");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << "->" << ret->_value << endl;}else{cout << "无此单词,请重新输⼊" << endl;}}
}
void  test2()
{string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠","苹果", "⾹蕉", "苹果", "⾹蕉" };BSTree<string, int> countTree;for (const auto& str : arr){// 先查找⽔果在不在搜索树中 // 1、不在,说明⽔果第⼀次出现,则插⼊<⽔果, 1> // 2、在,则查找到的结点中⽔果对应的次数++ //BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}int main()
{test1();test2();return 0;
}

六、总结

本章节和之后的文章使用的语言从C变为C++
优点与缺点:

  • 优点:
    • 查找、插入、删除操作的时间复杂度在平均情况下为 ,效率较高,特别是对于经常进行动态插入和查找操作的数据集合非常适用。
    • 中序遍历可以方便地得到有序的数据序列,无需额外的排序操作。
  • 缺点:
    • 最坏情况下(例如树退化为链表),查找、插入、删除操作的时间复杂度会退化到 ,性能下降严重。
    • 二叉搜索树的平衡情况依赖于插入和删除操作的顺序,如果数据的插入顺序不合理,可能会导致树的不平衡,影响操作的效率。
  • 应用场景:
    • 二叉搜索树常用于实现动态集合的数据结构,例如在数据库索引、文件系统的目录结构、编译器的符号表等场景中广泛应用,能够快速地进行数据的查找、插入和删除操作。

二叉搜索树在 C++ 中是一种重要的数据结构,掌握其基本操作和遍历方式对于解决各种实际问题具有重要意义。

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

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

相关文章

茶思屋直播|TinyEngine+AI:聚焦主航道,在实践中探索低代码技术黑土地

低代码引擎使能开发者定制低代码平台。它是低代码平台的底座&#xff0c;提供可视化搭建页面等基础能力&#xff0c;既可以通过线上搭配组合&#xff0c;也可以通过cli创建个人工程进行二次开发&#xff0c;实时定制出自己的低代码平台。适用于多场景的低代码平台开发&#xff…

GIS开发常用的开源地图数据框架有哪些?

学完Web前端开发&#xff0c;还需要掌握哪些内容&#xff1f;本篇文章再给大家主要讲讲针对WebGIS开发的地图和可视化数据库。 Echarts ECharts是一个使用 JavaScript 实现的开源可视化库。它可以流畅的运行在 PC 和移动设备上&#xff0c;兼容当前绝大部分浏览器&#xff08;…

LTE SSS产生过程中z序列的介绍和MATLAB以及C语言实现

参考3GPP 36.211 6.11章节 接上期介绍完LTE SSS产生过程中加扰序列c的产生和代码实现&#xff0c;本期介绍一下LTE SSS产生过程中加扰序列z的原理和MATLAB以及C语言的实现。 SSS产生过程中用到的加扰Z1序列有两个&#xff0c;一个是 另一个是 两个序列是由一个m序列z移位而来…

“一屏显江山”,激光显示重构「屏中世界」

【潮汐商业评论/原创】 2024年国庆期间&#xff0c;曾感动过无数国人的舞蹈诗剧《只此青绿》改编的同名电影即将上映&#xff0c;而这一次观众们不必走进电影院&#xff0c;在家里打开官方合作的海信激光电视也能享受到同等的视听效果&#xff0c;这是激光电视在观影场景领域的…

【C++ 基础数学 】2121. 2615相同元素的间隔之和|1760

本文涉及的基础知识点 基础数学 LeetCode2121. 相同元素的间隔之和 难度分&#xff1a;1760 令2165&#xff0c;和此题几乎相等。 给你一个下标从 0 开始、由 n 个整数组成的数组 arr 。 arr 中两个元素的 间隔 定义为它们下标之间的 绝对差 。更正式地&#xff0c;arr[i] 和…

Linux 进程2

环境变量 再Linux操作系统中一切皆文件&#xff0c;这个环境变量自然也是一个文件&#xff0c;它的作用是辅助我们使用操作系统还可以辨识我们是什么用户(一般用户&#xff0c;root用户)。 env是读取完整环境变量的指令&#xff0c;里面记录了许多我登录操作系统所用的用户的信…

apache paimon简介(官翻)

介绍 如下架构所示: 读/写操作: Paimon 支持多样化的数据读写方式,并支持 OLAP 查询。 读取: 支持从历史快照(批处理模式)中消费数据,从最新偏移量(流处理模式)中读取数据,或以混合方式读取增量快照。写入: 支持从数据库变更日志(CDC)进行流式同步,从离线数据中…

构建高效企业客户管理系统:SpringBoot应用

1 绪论 1.1研究背景 随着网络不断的普及发展&#xff0c;企业客户管理系统依靠网络技术的支持得到了快速的发展&#xff0c;首先要从员工的实际需求出发&#xff0c;通过了解员工的需求开发出具有针对性的首页、个人中心、员工管理、客户信息管理、行业类型管理、项目信息管理、…

LabVIEW界面输入值设为默认值

在LabVIEW中&#xff0c;将前面板上所有控件的当前输入值设为默认值&#xff0c;可以通过以下步骤实现&#xff1a; 使用控件属性节点&#xff1a;你可以创建一个属性节点来获取所有控件的引用。 右键点击控件&#xff0c;选择“创建” > “属性节点”。 设置属性节点为“D…

HDMI20协议解析_Audio_Clock_Regeneration

HDMI20协议解析_Audio_Clock_Regeneration 1.版本说明 日期作者版本说明20240918风释雪初始版本 2.概述 当通过HDMI传输音频信号时&#xff0c;Audio Clock Regeneration&#xff08;ACR&#xff09;是必须要传输的数据包之一&#xff1b; HDMI传输过程中&#xff0c;音频采样…

CentOS7搭建Hadoop3集群教程

一、集群环境说明 1、用VMware安装3台Centos7虚拟机 2、虚拟机配置&#xff1a;2C&#xff0c;2G内存&#xff0c;50G存储 3、集群架构设计 从表格中&#xff0c;可以看出&#xff0c;Hadoop集群&#xff0c;主要有2个模块服务&#xff0c;一个是HDFS服务&#xff0c;一个是YAR…

8.进销存系统(基于springboot的进销存系统)

目录 1.系统的受众说明 2.开发技术与环境配置 2.1 SpringBoot框架 2.2 Java语言简介 2.3 MySQL环境配置 2.4 idea介绍 2.5 mysql数据库介绍 2.6 B/S架构 3.系统分析与设计 3.1 可行性分析 3.1.1 技术可行性 3.1.2 操作可行性 3.1.3经济可行性 3.4.1 数据库…

第159天:安全开发-Python-协议库爆破FTPSSHRedisSMTPMYSQL等

目录 案例一: Python-文件传输爆破-ftplib 库操作 ftp 协议 ​案例二&#xff1a; Python-数据库爆破-redis 库操作redis 协议 案例三&#xff1a;Python-邮件爆破-smtplib 库操作 smtp 协议 案例四&#xff1a;Python-登录爆破-paramiko库操作ssh协议 案例五&#xff1a…

Web 服务器介绍 | 通过 Tomcat 说明其作用

1. 什么是 Web 服务器&#xff1f; Web服务器是一种用于存储、处理和提供网页或Web应用程序的服务器。它通过HTTP&#xff08;超文本传输协议&#xff09;与客户端&#xff08;通常是浏览器&#xff09;进行通信&#xff0c;接收客户端的请求&#xff08;如网页请求&#xff0…

【RPA私教课:UIPath】RPA 赋能科技企业,登录时验证码自动截取

在某科技型企业里&#xff0c;专门设置了一个验证码接收系统。每当用户进行登录操作时&#xff0c;都必须从这个系统中抓取最新的登录验证码&#xff0c;以确保登录的安全性。 具体需求如下&#xff1a; 客户会预先在表格中妥善保存众多的账户和密码。当 RPA 机器人在业务系统…

Vue使用axios实现Ajax请求

1、什么是 axios 在实际开发过程中&#xff0c;浏览器通常需要和服务器端进行数据交互。而 Vue.js 并未提供与服务器端通信的接口。从 Vue.js 2.0 版本之后&#xff0c;官方推荐使用 axios 来实现 Ajax 请求。axios 是一个基于 promise 的 HTTP 客户端。 关于 promise 的详细介…

从零开始讲DDR(3)——DDRC与DDRPYH

一、DDR的使用 在之前的文章中我们介绍了DDR的基本概念&#xff0c;但是DDR内存的操作不仅仅是简单的数据读取和写入&#xff0c;它包括许多时序要求和信号调度。为了让DDR内存有效运作&#xff0c;系统需要在逻辑层和物理层之间进行大量的协作。我们拿出一张DDR的操作简化状态…

rfid超高频手持移动终端的性能特点

超高频手持移动终端的工作频率范围通常在860 MHz至960 MHz之间&#xff0c;具体频段根据不同的地区和国家有所不同&#xff0c;下面我们就一起来了解一下&#xff0c;超高频手持移动终端的性能特点。 超高频手持移动终端的性能特点 高效率与高速度&#xff1a;超高频手持移动…

付费计量应用过程(Payment Metering Application process)

The Payment Metering Application process is the combination of the business and support processes as the resultant interactions between the business and support functions, which thus describes the dynamic behavior of the system as a whole. 付费计量…

C++ std::any升级为SafeAny

std::any测试 #include <any>class A { public:int8_t a; };int main(int argc, char* argv[]) {std::any num((int8_t)42);auto a std::any_cast<A>(num);return 0; }异常&#xff1a; 0x00007FFA9385CD29 处(位于 test.exe 中)有未经处理的异常: Microsoft C 异…