文章目录

  • 前言
  • 二叉搜索树(二叉排序树或二叉查找树)
    • 二叉搜索树的模拟实现
    • 二叉搜索树和有序数组二分查找的比较
    • 两个搜索模型
  • 作业部分

前言

二叉搜索树(Binary Search Tree,简称 BST)作为一种重要的树形数据结构,在计算机科学领域有着广泛的应用。它凭借其基于键值的有序性,能够高效地支持数据的插入、删除和查找等操作,是许多复杂算法和系统的基础组件。

本文将围绕二叉搜索树展开全面而深入的探讨。首先,我们将从其核心定义和关键性质出发,帮助读者建立对二叉搜索树的基本认知,包括其通过中序遍历可得到升序序列这一重要特性。随后,详细剖析二叉搜索树的各项基本操作,如插入、查找、删除等,并通过 C++ 代码实现进行具体演示,同时对比递归与非递归实现方式的异同。

此外,我们还将对二叉搜索树与有序数组的二分查找进行对比分析,明确各自的优势与局限。最后,结合一系列经典的算法题目,如二叉搜索树与双向链表的转换、根据遍历序列构造二叉树、二叉树的非递归遍历等,展示二叉搜索树在实际问题中的应用,帮助读者巩固所学知识,提升解决复杂问题的能力。无论是数据结构初学者,还是希望深化对二叉搜索树理解的开发者,都能从本文中获得有价值的参考。

二叉搜索树(二叉排序树或二叉查找树)

概念:是一颗空树或者是具有以下性质的二叉树:

1.若左子树不为空,则左子树上所有节点的值都小于根节点的值

2.若右子树不为空,则右子树上所有节点的值都大于根节点的值

3.它的左右子树也分别为二叉搜索树

引申:a.用中序遍历去遍历二叉搜索树的结果正好是升序

    b.查找其中元素的最坏时间复杂度是O(n)--n表示n个元素

比如:在这里插入图片描述

c.结点左边所有的树都比结点小;结点右边所有的树都比结点大

二叉搜索树的模拟实现

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:BSTree():_root(nullptr){}BSTree(const BSTree<K>& t)
{_root = Copy(t._root);
}BSTree<K>& operator=(BSTree<K> t)
{swap(_root, t._root);//理解!return *this;
}~BSTree()
{Destroy(_root);
}bool Insert(const K& key)//插入不了重复的值{if (_root == nullptr){_root = new Node(key);return true;}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;//cur只是里面存的值变成了他的}else{return false;}}cur = new Node(key);//把开辟好的地址存在了cur里面if (key > parent->_key){parent->_right = cur;//本来里面是nullptr;}else{parent->_left = cur;}return true;}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;}else{return true;}}return false;}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 (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}// 右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}	} // 左右都不为空 else{// 找替代节点Node* parent = cur;//这里不能是nullptr,不然要删的地方是根节点就惨了Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}swap(cur->_key, leftMax->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;
//这个就是左子树存在右子树的情况}//这里要注意!!!cur = leftMax;}delete cur;return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}/*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);
}*/用递归实现这些函数private:/*bool _EraseR(Node*& root, const K& key)这种树形递归的话一般都要传这个root{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;// 1、左为空// 2、右为空// 3、左右都不为空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;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);这里不能是(leftMax,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{return false;}}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}}*/这是用递归实现的函数void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}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 Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}
private:Node* _root;
};

引申:1.swap对于基本类型,交换的是值;对于指针,交换的是指针里面存的值

    2.二叉搜索树的删除操作的模拟实现做法:
找到后分情况:
1.结点没有孩子--直接删
2.结点有一个孩子--托孤
3.结点有两个孩子--替换法:该结点跟左子树的最右(最大)结点或右子树的最左(最小)结点替换,然后删除
3.关于引用,平时说的话就是不开空间,探讨底层汇编才考虑它其实是占了空间的

二叉搜索树和有序数组二分查找的比较

二分查找的话,插入和删除的效率不太行

二叉搜索树的话,查找排序插入删除效率都不错,但是下限太低了,如图:

在这里插入图片描述

两个搜索模型

1.key的搜索模型:快速判断在不在

eg:门禁系统 小区车辆出入系统

比如:检查一篇英文文章,看单词是否拼写正确
做法:读取词库到一颗搜索树上,读取单词,看在不在-不在就是拼写错误

2.key/value的搜索模型:通过一个值找另外一个值

eg:商场的车辆出入系统 高铁实名制车票系统

举例:统计水果出现的次数
做法:第一次出现的话,要插入这个水果名和它出现的次数为1不是第一次的话,直接次数++就行了

注意:key一般是不允许重复的信息!

k-v模型的话,在上面的二叉搜索树的模拟实现里面,成员变量要加个value,Inserterase操作的形参要加value

作业部分

下面关于二叉搜索树正确的说法是(C)
A.待删除节点有左子树和右子树时,只能使用左子树的最大值节点替换待删除节点
B.给定一棵二叉搜索树的前序和中序遍率历结果,无法确定这棵二叉搜索树
C.给定一棵二叉搜索树,根据节点值大小排序所需时间复杂度是线性的
D.给定一棵二叉搜索树,可以在线性时间复杂度内转化为平衡二叉搜索树引申:使用两个遍历结果确定树的结构, 其中有一个遍历结果必须要是中序遍历结果,这样就可以

牛客网 JZ36 二叉搜索树与双向链表

牛客网    JZ36 二叉搜索树与双向链表
做法:也就是把当前在的地方的left改成指向中序遍历上一个结点,把right改成指向中序遍历下一个结点在前序遍历改结点的时候:prev一定要传引用
注意:最后返回的结点多半不是开头给的那个结点易忘这一步:if(prev) prev->right = cur;
代码展示:
/*
struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {}
};*/void Inorder(TreeNode*cur,TreeNode*&prev)//这个要注意
{if(cur == nullptr) return;Inorder(cur->left,prev);//第二个传cur会出事cur->left = prev;if(prev) prev->right = cur;//这个不能省,叶子结点需要这个prev = cur;Inorder(cur->right, prev);
}class Solution {
public:TreeNode* Convert(TreeNode* pRootOfTree) {TreeNode* prev = nullptr;Inorder(pRootOfTree, prev);while(pRootOfTree&&pRootOfTree->left) pRootOfTree = pRootOfTree->left;return pRootOfTree;}
};

在这里插入图片描述

引申:whileif自己老是容易写混

力扣 606. 根据二叉树创建字符串

力扣 606. 根据二叉树创建字符串
这道题最主要是()的处理:
1.左边为空,右边不为空->左边右边括号都保留
2.左边不为空,右边为空->左边括号保留
3.左边右边都为空->左边右边的括号都不保留注意:root->val记得转化成string类型的
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:string tree2str(TreeNode* root) {string s1;if(root == nullptr) return "";else s1+=to_string(root->val);if(root->left||root->right){s1+="(";s1+= tree2str(root->left); s1+=")";}if(root->right)//{s1+="(";s1+=tree2str(root->right); s1+=")";}return s1;}
};

在这里插入图片描述

注意理解这里局部变量s1的用法

力扣 236. 二叉树的最近公共祖先

力扣   236. 二叉树的最近公共祖先
做法1:时间复杂度为O(N平方)
先查找,看p,q看该结点的左边还是右边->一个在左一个在右这个结点就是最近公共祖先在同一边就要递归去那一边再来
注意:p,q其中一个可能就是最近公共祖先做法2:时间复杂度为O(n)
搞了两个栈,把到p,q的路径存在了栈里面,之后根据他们的公共祖先高度应该一样来找
代码展示:
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool Find(TreeNode*root,TreeNode*target,stack<TreeNode*>&path)//{if(root == nullptr) return false;path.push(root);if(root == target) return true;if(Find(root->left,target,path)) return true;if(Find(root->right,target,path)) return true;path.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {stack<TreeNode*> p1,q1;Find(root,p,p1);Find(root,q,q1);while(p1.size()>q1.size())   p1.pop();while(p1.size()<q1.size())   q1.pop();while(p1.top()!=q1.top()){p1.pop(); q1.pop();}return p1.top();}
};

在这里插入图片描述

上面是做法2的Find的写法

力扣 105. 从前序与中序遍历序列构造二叉树

力扣    105. 从前序与中序遍历序列构造二叉树
做法: 用前序去确定根的位置,用中序去分割左右区间(也就是中序去判断完没完)
注意:root创建空间的写法: TreeNode*root = new TreeNode(preorder[previ]);previ要带引用,传参时不能传个0过去这样!
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:TreeNode* _build(vector<int>& preorder, vector<int>& inorder,int&previ,int begini,int endi) {if(begini>endi) return nullptr;TreeNode*root = new TreeNode(preorder[previ]);int rooti = begini;while(rooti<=endi){if(preorder[previ] == inorder[rooti])   break;rooti++;}++previ;root->left = _build(preorder,inorder,previ,begini,rooti-1);root->right = _build(preorder,inorder,previ,rooti+1,endi);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int n = (int)preorder.size()-1; int p = 0;return _build(preorder,inorder,p,0,n);}
};

自己实现的函数:在这里插入图片描述

力扣 106. 从中序与后序遍历序列构造二叉树
力扣 144. 二叉树的前序遍历(自己要求自己用非递归的形式完成)
力扣 94. 二叉树的中序遍历(自己要求自己用非递归的形式完成)
力扣 145. 二叉树的后序遍历(自己要求自己用非递归的形式完成)

力扣 106. 从中序与后序遍历序列构造二叉树
跟上面做法的区别:previ要从最后开始取,然后根构建完之后先构建右树代码展示:
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:
TreeNode* _build(vector<int>& postorder, vector<int>& inorder,int&previ,int begini,int endi) {if(begini>endi) return nullptr;//TreeNode*root = new TreeNode(postorder[previ]);int rooti = begini;while(rooti<=endi){if(postorder[previ] == inorder[rooti])   break;rooti++;}--previ;root->right = _build(postorder,inorder,previ,rooti+1,endi);root->left = _build(postorder,inorder,previ,begini,rooti-1);return root;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {int p = postorder.size()-1;return _build(postorder,inorder,p,0,postorder.size()-1);}
};
力扣 144. 二叉树的前序遍历(自己要求自己用非递归的形式完成)
做法:访问左路结点(此时就把结点的值搞到vector里面),左路结点全入栈,后续依次访问左路结点的右子树力扣   94. 二叉树的中序遍历(自己要求自己用非递归的形式完成)
做法:改成在出栈的时候再把结点存vector里就行了
代码展示:
中序遍历:
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int>v;  stack<TreeNode*> st;TreeNode* cur = root;while(cur||!st.empty()){while(cur){st.push(cur);cur = cur->left;}TreeNode*top = st.top();st.pop();   v.push_back(top->val);cur = top->right;}return v;}
};前序遍历:
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {vector<int>v;  stack<TreeNode*> st;TreeNode* cur = root;while(cur||!st.empty()){while(cur){v.push_back(cur->val);st.push(cur);cur = cur->left;}TreeNode*top = st.top();st.pop();cur = top->right;}return v;}
};
力扣  145. 二叉树的后序遍历(自己要求自己用非递归的形式完成)
做法:加一个prev来记录上一个存了的结点是啥改成在一个结点的右子树为空或者上一个访问结点是右子树的根时再访问这个结点下面是与前面两道题相差比较大的地方

在这里插入图片描述

代码展示:
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {vector<int>v;  stack<TreeNode*> st;TreeNode* cur = root; TreeNode*prev = nullptr;while(cur||!st.empty()){while(cur){st.push(cur);cur = cur->left;}TreeNode*top = st.top();     if(top->right== nullptr||top->right == prev){v.push_back(top->val);prev = top;st.pop();}elsecur = top->right;}return v;}
};

二叉树的后序非递归遍历中,需要的额外空间包括(C)
A.一个栈
B.一个队列
C.一个栈和一个记录标记的顺序表
D.一个队列和一个记录标记的顺序表
注意:vector别忘了他是顺序表!

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

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

相关文章

牛客.空调遥控二分查找牛客.kotori和气球(数学问题)力扣.二叉树的最大路径和牛客.主持人调度(二)

目录 牛客.空调遥控 二分查找 牛客.kotori和气球&#xff08;数学问题) 力扣.二叉树的最大路径和 牛客.主持人调度(二) 牛客.空调遥控 枚举n个空调之后&#xff0c;使数组有序&#xff0c;左右下标&#xff0c;用二分查找&#xff0c;然后一个求 长度就好 二分查找 /二分理…

《嵌入式Linux应用编程(二):标准IO高级操作与文件流定位实战》

今日学习内容1. 行输入函数安全实践(1) fgets vs gets函数安全特性换行符处理缓冲区保护fgets指定读取长度&#xff08;size-1&#xff09;保留\n并添加\0安全&#xff08;防溢出&#xff09;gets无长度限制将\n替换为\0危险2. Linux标准文件流文件流符号设备 标准输入stdin键盘…

Springboot2+vue2+uniapp 小程序端实现搜索联想自动补全功能

目录 一、实现目标 1.1 需求 1.2 实现示例图: 二、实现步骤 2.1 实现方法简述 2.2 简单科普 2.3 实现步骤及代码 一、实现目标 1.1 需求 搜索联想——自动补全 &#xff08;1&#xff09;实现搜索输入框&#xff0c;用户输入时能显示模糊匹配结果 &am…

极简 5 步:Ubuntu+RTX4090 源码编译 vLLM

极简 5 步&#xff1a;UbuntuRTX4090 源码编译 vLLM1. 系统依赖&#xff08;一次性&#xff09;2. 进入源码目录 & 激活环境3. 启用 ccache 自动并行度4. 拉代码 编译&#xff08;2 行搞定&#xff09;5. 更新 flash-attn&#xff08;与 vLLM 配套&#xff09;6. 启动 4 …

生产工具革命:定制开发开源AI智能名片S2B2C商城小程序重构商业生态的范式研究

摘要互联网作为信息工具已深刻改变商业生态&#xff0c;但其本质仍停留在效率优化层面。本文提出&#xff0c;基于定制开发开源AI智能名片与S2B2C商城小程序的深度融合&#xff0c;正在引发生产工具层面的革命性变革。该技术架构通过重构"人-货-场"关系&#xff0c;实…

Transformer前传:Seq2Seq与注意力机制Attention

前言 参考了以下大佬的博客 https://blog.csdn.net/v_july_v/article/details/127411638 https://blog.csdn.net/andy_shenzl/article/details/140146699 https://blog.csdn.net/weixin_42475060/article/details/121101749 https://blog.csdn.net/weixin_43334693/article/det…

企业架构工具篇之ArchiMate的HelloWorld(2)

本文通过ArchiMate做一个员工报销流程设计的小demo,按照步骤都可以做出来,在做这个demo之前先简单认识下Archimate的开发界面: 模型树(Models)窗口:通常位于左上方,以树形结构展示一个或多个 ArchiMate 模型。用户可在此浏览模型的整体结构,快速定位到特定的模型元素,…

Docker 详解(保姆级安装+配置+使用教程)

文章目录一、初识 Docker二、Docker 命令1、安装2、配置镜像加速器检查配置是否生效3、服务相关命令4、镜像相关命令5、容器相关命令三、Docker 容器数据卷1、数据卷概念2、数据卷作用3、配置数据卷4、配置数据卷容器四、Docker 应用部署五、备份与迁移六、Dockerfile七、Docke…

做调度作业提交过程简单介绍一下

✅作业提交与执行流程前文提到在 Linux 的 HPC 或超算环境中&#xff0c;可以只在共享存储上安装一次应用程序&#xff0c;然后所有计算节点通过挂载共享目录来访问和执行这些程序&#xff0c;那么作业提交及执行过程是怎么样的流程呢&#xff1f;结构说明&#xff1a;第一行是…

【Altium designer】解决报错“Access violation at address...“

问题现象如上AD9原理图工程所示报错&#xff0c;当我关闭这个“CMM-WEIER-VA”原理图工程以及其他不相关的原理图工程出现报错&#xff1a;Access violation at address 0832A5EC in module WorkspaceManager.DLL. Read of address 00000061 at 0832A5EC&#xff0c;任务管理器…

小杰python高级(three day)——numpy库

1.numpy数组的操作&#xff08;1&#xff09;数组的连接stack该函数可以实现多个数组的堆叠(连接)&#xff0c;会创建新的轴&#xff0c;用于沿着新的轴连接一系列数组&#xff0c;所有数组必须具有相同的形状。可以增加数组的维度。假设输入的每个数组都是 n 维数组&#xff0…

视频剪辑的工作流程

准备素材 1.准备音频&#xff0c;视频、图片等素材 2.准备Pr创建的序列、彩条、字母、倒计时片头等功能性素材 创建项目 创建项目是诗篇剪辑的第一步&#xff0c;创建一个指定名称与存放位置的项目文件&#xff0c;用来通义管理整个视频项目创建序列 序列决定剪辑的尺寸、帧速率…

下一个排列 的 思路总结

文章目录思路分析&#xff1a; 倒序遍历&#xff1a;题目要求的是下一个排列&#xff0c;那么肯定数字的跳跃不能太大&#xff0c;所以可以比较好确定的是&#xff0c;遍历的顺序是倒序遍历比较方向&#xff1a;对于每一个数字&#xff0c;需要找到右边最大的比它小的数字&…

Spring Cloud-面试题(49)

摘要&#xff1a; 1、通俗易懂&#xff0c;适合小白 2、仅做面试复习用&#xff0c;部分来源网络&#xff0c;博文免费&#xff0c;知识无价&#xff0c;侵权请联系&#xff01; 1. 什么是Spring Cloud框架&#xff1f;子项目哪几大类&#xff1f; Spring Cloud是一套分布式系…

资源查看-iostat命令

文章目录 系统中未安装 iostat 命令 1. 监控CPU与磁盘的基础负载 2. 诊断I/O性能瓶颈 3. 实时监控与动态采样 4. 特定设备或分区的精细化监控 5. 性能测试与基准数据生成 6. 结合其他工具进行综合调优 总结 结果输出速查表 第一部分:CPU统计信息 第二部分:设备/磁盘统计信息(…

STM32 HAL库外设编程学习笔记

STM32 HAL库外设编程 1. 概述 本文档是基于STM32 HAL库的外设编程学习笔记&#xff0c;主要包括以下外设的配置和使用方法&#xff1a; GPIO&#xff1a;通用输入输出接口ADC&#xff1a;模数转换器UART&#xff1a;通用异步收发器TIM&#xff1a;定时器I2C&#xff1a;内部…

DHCP服务配置与管理实战指南

DHCP 服务配置与管理笔记 一、DHCP 核心概念 1. DHCP 定义与功能 DHCP (Dynamic Host Configuration Protocol)&#xff1a;动态主机配置协议核心功能&#xff1a; 自动分配 IP 地址提供子网掩码、网关、DNS 等网络参数管理 IP 地址租约周期 典型应用&#xff1a;ADSL拨号、企业…

WebSocket 在多线程环境下处理 Session并发

WebSocket 在多线程环境下处理 Session并发时&#xff0c;常见问题包括状态冲突&#xff08;如 IllegalStateException&#xff09;、消息乱序、连接超时等。以下是综合各技术方案的解决方案&#xff0c;分为单机多线程和分布式集群两类场景&#xff1a;&#x1f512; 一、单机…

JDBC的连接过程(超详细)

JDBC&#xff08;Java Database Connectivity&#xff09;是 Java 用于访问数据库的标准 API&#xff0c;它允许 Java 程序与各种不同类型的数据库进行交互&#xff0c; 其连接数据库的过程主要包含以下几个步骤&#xff1a;1. 导入 JDBC 驱动依赖在使用 JDBC 连接数据库之前&a…

本地WSL部署接入 whisper + ollama qwen3:14b 总结字幕校对增强版

1. 实现功能 M4-4: 校对增强版 (最终完全体) 本脚本是整个 Module 的最终形态&#xff0c;采用了“代码预处理 LLM校对”的终极方案&#xff1a; 代码预处理: 确定性地在每个语音片段后添加逗号&#xff0c;生成一份“标点草稿”。LLM校对: LLM 的任务被简化为“校对和修正”这…