【AVL树】—— 我与C++的不解之缘(二十三)
什么是AVL树?
AVL树发明者是G. M. Adelson-Velsky和E. M. Landis两个前苏联科学家,他们在1962年论文《An algorithm for the organization of information》中发表了AVL树。AVL树是最先发明的自平衡二叉搜索树,说白了就是能够自己控制平衡结构的一个二叉搜索树;AVL可以是一个空树,或者其左右树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树,左右子树的高度差不超过一,而不是0?(如果一棵树的节点个数是2、4等的情况下,高度差最好情况就是1,到不到0。- 本篇在实现
AVL树时,引入了一个新的概念(平衡因子);每个节点都存在平衡因子,平衡因子等于右子树的高度减去左子树的高度,这样平衡因子的取值就是(0、1、-1);(平衡因子也不是必须的,这里引入平衡因子这一概念,方便观察和控制整个AVL树的平衡。
简单来说,AVL树就是一个特殊的搜索二叉树,特殊就特殊在它可以控制平衡,保持左右子树的高度差不超过1。
那又是如何实现的呢?
AVL树的实现
1. AVL树的结构
先来看一下
AVL树的结构,首先就是AVL树的节点
template<class K,class V>
struct TreeNode {int _bf;pair<K, V> _kv;TreeNode<K, V>* _left;TreeNode<K, V>* _right;TreeNode<K, V>* _parent;TreeNode(const pair<K, V> kv):_kv(kv), _bf(0), _left(nullptr), _right(nullptr), _parent(nullptr){}
};
这里并没有直接存储数据K,和V,而是像map那样将其封装成一个pair<K,V>类型。
再来看一下
AVL树都要实现哪些方法
template<class K,class V>
class AVLTree
{typedef TreeNode<K, V> Node;
public://插入bool insert(const pair<K, V> kv) {}//查找bool find(const K& kev) {}//中序遍历void order() {}
private://右单旋void RevoleR(Node* parent) {}//左单旋void RevoleL(Node* parent) {}//右左双选void RevoleRL(Node* parent) {}//左右双选void RevoleLR(Node* parent) {}//中序遍历void order(Node* root) {}Node* _root;
};
这里实现了几个私有的成员方法,因为这些方法不希望在类外被直接访问。(其中order()是为了实现中序遍历,因为在类外无法访问到该树的根节点。)
2. AVL树的插入
插入过程
对于插入数据的整个过程,其实就是在搜索二叉树的基础上,增加了更新平衡因子和在更新平衡因子的过程中需要旋转的情况就行旋转。
- 按搜索二叉树的规则进行插入数据
- 新增节点以后,就可能会影响到部分祖先节点的平衡因子,所以从新增节点 -> 根节点这整个路径上节点的平衡因子(在更新的过程中,可能会遇到继续更新,更新结束以及需要旋转的情况。)
- 更新平衡因子过程中没有出现问题,插入就结束了。
- 在平衡的过程中,出现了不平衡的情况,就要堆不平衡子树进行旋转,选择后调平衡的同时,也降低了子树的高度,就不会影响上一层的平衡因子,插入也就结束了。
更新平衡因子
首先,平衡因子=右子树高度-左子树高度
- 插入节点会增加高度,所以,新增节点如果是在
parent节点的右子树,则parent节点的平衡因子++;如果是在parent节点的左子树,那么parent节点的平衡因子–;parent所在子树的高度是否变化就决定了是否要继续往上更新平衡因子。
更新平衡因子可能遇到的情况:
- 更新之后
parent节点平衡因子等于0:更新过程中parent的平衡因子变化-1->0或者1->0,这说明了插入节点之前parent子树一边高一边低,新增节点插入到了低的那一边,插入节点后以parent为根节点的子树的高度不变,就不会影响其父节点的平衡因子(就不会影响到上面节点的平衡)所以更新就结束了。- 更新之后
parent节点平衡因子等于1或-1:更新过程中parent的平衡因子变化0->-1或者0->1,这就说明了,插入节点之前,parent的左右子树高度相同了,插入节点之后parent子树的高度发生了变化,所以就会影响其父节点的平衡因子,从而影响上面节点的平衡;所以需要继续更新平衡因子。- 更新之后
parent节点平衡因子等于2或者-2:更新过程中parent的平衡因子变化1->2或者-1->-2,这说明,在插入节点之前,以parent为根节点的子树就已经一边高一边低了;然后新增节点还插入到了高的那一边,这样以parent为根节点的子树就已经不满足AVL树的结构了,此时就需要对该树就行旋转(旋转:一是将以parent为根节点的子树调整平衡,二是降低以parent为根节点的子树的高度,回复到插入以前的高度);旋转完成后,就不需要继续更新平衡因子了。
更新之后parent节点平衡因子为0

更新之后parent节点平衡因子为1或者-1

更新之后parent节点平衡因子为2或者-2

更新平衡因子的过程实现
bool insert(const pair<K, V> kv)
{Node* newnode = Node(kv);if (_root == nullptr){_root = newnode;return true;}Node* parent = nullptr;Node* pcur = _root;while (pcur){if (kv.first > pcur->_kv.first){parent = pcur;pcur = pcur->_right;}else if (kv.first < pcur->_kv.first){parent = pcur;pcur = pcur->_left;}else{return false;}}pcur = newnode;newnode->_parent = parent;if (kv.first > parent->_kv.first){parent->_right = pcur;}else if (kv.first < parent->_kv.first){parent->_left = pcur;}else{return false;}//更新平衡因子while (parent){if (pcur == parent->_left){--parent->_bf;}else{++parent->_bf;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){pcur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//旋转}} return true;
}
3.旋转
新插入节点以及更新平衡因子如上述,那么在更新平衡因子过程中,遇到平衡因子等于2(也就是以parent为根节点的子树不平衡)需要进行旋转,那如何旋转的呢?
旋转的规则:
- 保持搜索树的原则。
- 将要旋转的树从不满足到满足平衡,其次降低树的高度。
旋转一共分为四种(左单旋、右单旋、左右双旋、右左双旋),其每一种旋转都对应一种情况;
左单旋
先来看一下这种情况:

如上图中以
5为根节点的子树,其中a、b、c都是高度为h的子树(h可以等于0);现在要在a子树中插入一个节点,在更新平衡因子的过程中,5所在节点的平衡因子1 -> 2,此时该子树就不平衡了,需要进行旋转;
通过观察上图,我们可以发现,5节点的右子树太高了,所以我们需要向左旋转来平衡高度;
如何旋转呢?
旋转步骤
b子树变成5节点的右子树;- 以
5节点为根节点的子树变成10节点的左子树;10节点就变成了这个子树新的根节点。

其中5<
b子树<10,所以将b子树变成5的右子树,以5为根节点的子树变成10的左子树,仍然满足搜索二叉树的规则;然后
10节点变成了这部分子树新的根节点。(并不一定是整个子树新的根节点)。
代码实现:
//左单旋
void RevoleL(Node* parent)
{//旋转节点的右孩子节点Node* subr = parent->_right;//旋转节点的右孩子节点的左孩子节点Node* subrl = parent->_right->_left;//subrl变成parent的右子树parent->_right = subrl;//subrl可能为空if (subrl)subrl->_parent = parent;//parent->变成subr的左子树subr->_left = parent;//记录parent的父节点Node* pnode = parent->_parent;parent->_parent = subr;//如果parent是整个avl树的根节点if (pnode == nullptr){_root = subr;subr->_parent = nullptr;}else{//parent父节点不为空subr->_parent = pnode;if (pnode->_left == parent){pnode->_left = subr;}else{pnode->_right = subr;}}//调整完之后将parent节点与subr节点的平衡因子修改成0parent->_bf = 0;subr->_bf = 0;
}
右单旋
了解了左单旋,右单旋就十分简单了:

和左单旋的情况相似,有单旋就是10节点的左子树高,需要进行右单旋;
旋转步骤
b子树变成10节点的左子树;- 以
10节点为根节点的子树变成5节点的右子树;5节点就变成这部分子树的根节点。

其中
5<b子树<10,所以将b子树变成10的左子树,以10为根节点的子树变成5的右子树;仍然保持搜索二叉树的结构。
5节点就变成了这部分子树的根节点。
代码实现
//右单旋
void RevoleR(Node* parent)
{//旋转节点的左孩子节点Node* subl = parent->_left;//旋转节点的左孩子节点的右孩子节点Node* sublr = parent->_left->_right;//sublr变成parent的左孩子节点parent->_left = sublr;//sublr可能为nullptrif (sublr)sublr->_parent = parent;//parent变成subl的右孩子节点subl->_right = parent;//记录parent的父节点Node* pnode = parent->_parent;if (pnode == nullptr){_root = subl;subl->_parent = nullptr;}else{subl->_parent = pnode;if (pnode->_left == parent){pnode->_left = subl;}else{pnode->_right = subl;}}//修改parent 和 subl 的平衡因子parent->_bf = 0;subl->_bf = 0;
}
左右双旋
左单旋、右单旋都是纯粹的一边高(就是在parent的左/右孩子的左/右孩子所在子树中插入数据);按上述说,就是在a子树中插入数据,但是如果是在b子树中插入数据呢?

如上图,我们很显然不能单纯的使用右单旋或者左单旋来解决问题了;
旋转步骤
左右双旋其实就是,先对parent的左孩子节点进行一次左单选,再对parent节点进行一次右单旋;
来看分析:
这里h是能够等于0的,我们分开来讨论:
h=0

我们先对subl节点进行一次左单旋,再对parent节点进行一次右单旋;

h!=0
对于h!=0的情况,b子树中就至少有一个节点,那我们要分为两种情况讨论;
我们将一个avl树抽象成下面这种情况:

这样我们可以看出来,可能是在e子树中插入数据,也可能是在f子树中插入数据;那这两种情况就也要分开讨论:
在e子树中插入

此时,我们还是先对subl节点左单旋,变成纯粹的一边高,再对parent节点进行右单旋;

在f子树插入节点

还是先对subl左单旋,再对parent进行右单旋;

通过观察,我们可以发现,这三种情况都是进行了一次左单旋和一次右单旋,不同的是其结果中subl和parent的平衡因子不同。
这样我们在实现时,就直接复用左单旋和右单旋就好了,然后根据其平衡因子的情况来判断最后subl和parent节点的平衡因子即可。
更新平衡因子
sublr节点平衡因子等于0:sublr、subl和parent平衡因子都为0;sublr节点平衡因子等于-1:sublr和subl平衡因子等于0,parent平衡因子等于1;sublr节点平衡因子等于1:sublr和parent平衡因子等于0,subl平衡因子等于-1;
代码实现
代码实现过程中有一个细节就是:
在进行左右单旋时,会将平衡因子修改成
0,我们就需要先记录一下sublr原本的平衡因子,来保证我们单旋结束后的平衡因子的修改。
//左右双选void RevoleLR(Node* parent) {Node* subl = parent->_left;Node* sublr = parent->_left->_right;int bf = sublr->_bf;//对subl进行左单旋RevoleL(subl);//对parent进行右单旋RevoleR(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;subl->_bf = 0;sublr->_bf = 0;}else if (bf == 1){parent->_bf = 0;subl->_bf = -1;sublr->_bf = 0;}else if (bf == -1){parent->_bf = 1;subl->_bf = 0;sublr->_bf = 0;}}
右左双旋
右左双旋和左右双旋逻辑非常像,这里就不演示了,直接看代码实现:
//右左双选void RevoleRL(Node* parent) {Node* subr = parent->_right;Node* subrl = parent->_right->_left;int bf = subrl->_bf;RevoleR(subr);RevoleL(parent);if (bf == 0){parent->_bf = 0;subr->_bf = 0;subrl->_bf = 0;}else if (bf == 1){parent->_bf = -1;subr->_bf = 0;subrl->_bf = 0;}else if (bf == -1){parent->_bf = 0;subr->_bf = 1;subrl->_bf = 0;}}
在旋转实现完成之后我们就可以完善我们insert了:
//插入
bool insert(const pair<K, V> kv)
{Node* newnode = Node(kv);if (_root == nullptr){_root = newnode;return true;}Node* parent = nullptr;Node* pcur = _root;while (pcur){if (kv.first > pcur->_kv.first){parent = pcur;pcur = pcur->_right;}else if (kv.first < pcur->_kv.first){parent = pcur;pcur = pcur->_left;}else{return false;}}pcur = newnode;newnode->_parent = parent;if (kv.first > parent->_kv.first){parent->_right = pcur;}else if (kv.first < parent->_kv.first){parent->_left = pcur;}else{return false;}//更新平衡因子while (parent){if (pcur == parent->_left){--parent->_bf;}else{++parent->_bf;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){pcur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//旋转if (parent->_bf == 2 && parent->_left->_bf == 1){//左单旋RevoleL(parent);}else if (parent->_bf == 2 && parent->_left->_bf == -1){//右左双旋RevoleRL(parent);}else if (parent->_bf == -2 && parent->_right->_bf == -1){//右单旋RevoleR(parent);}else if (parent->_bf == -2 && parent->_left->_bf == 1){//左右双旋RevoleLR(parent);}}}return true;
}
旋转了解完以后,就可以完善之前的插入功能了:
//插入
bool insert(const pair<K, V> kv)
{Node* newnode = new Node(kv);if (_root == nullptr){_root = newnode;return true;}Node* parent = nullptr;Node* pcur = _root;while (pcur){if (kv.first > pcur->_kv.first){parent = pcur;pcur = pcur->_right;}else if (kv.first < pcur->_kv.first){parent = pcur;pcur = pcur->_left;}else{return false;}}pcur = newnode;newnode->_parent = parent;if (kv.first > parent->_kv.first){parent->_right = pcur;}else if (kv.first < parent->_kv.first){parent->_left = pcur;}else{return false;}//更新平衡因子while (parent){if (pcur == parent->_left){--parent->_bf;}else{++parent->_bf;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){pcur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//旋转if (parent->_bf == 2 && parent->_left->_bf == 1){//左单旋RevoleL(parent);}else if (parent->_bf == 2 && parent->_left->_bf == -1){//右左双旋RevoleRL(parent);}else if (parent->_bf == -2 && parent->_right->_bf == -1){//右单旋RevoleR(parent);}else if (parent->_bf == -2 && parent->_left->_bf == 1){//左右双旋RevoleLR(parent);}}}return true;
}
4. AVL树的查找
AVL树的查找先对就简单多了,和搜索二叉树查找一样。
//查找bool find(const K& kv) {Node* ptail = _root;while (ptail){if (kv.first > ptail->_kv->first){ptail = ptail->_right;}else if (kv.first < ptail->_kv->first){ptail = ptail->_left;}else{return true;}}return false;}
对于
AVL树的删除,有点过于复杂,感兴趣的可以深入探究一下;后面研究过了再来探讨这个问题。
}else if (parent->_bf == -2 && parent->_right->_bf == -1){//右单旋RevoleR(parent);}else if (parent->_bf == -2 && parent->_left->_bf == 1){//左右双旋RevoleLR(parent);}}
}
return true;
}
## 4. `AVL`树的查找`AVL`树的查找先对就简单多了,和搜索二叉树查找一样。```cpp//查找bool find(const K& kv) {Node* ptail = _root;while (ptail){if (kv.first > ptail->_kv->first){ptail = ptail->_right;}else if (kv.first < ptail->_kv->first){ptail = ptail->_left;}else{return true;}}return false;}
对于
AVL树的删除,有点过于复杂,感兴趣的可以深入探究一下;后面研究过了再来探讨这个问题。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws
相关文章:
【AVL树】—— 我与C++的不解之缘(二十三)
什么是AVL树? AVL树发明者是G. M. Adelson-Velsky和E. M. Landis两个前苏联科学家,他们在1962年论文《An algorithm for the organization of information》中发表了AVL树。AVL树是最先发明的自平衡二叉搜索树,说白了就是能够自己控制平衡结构…...
用大白话解释日志处理Log4j 是什么 有什么用 怎么用
Log4j是什么? Log4j就像程序的“黑匣子”,专门用来记录软件运行时的各种信息,比如哪里报错、性能如何、用户操作轨迹等。它是Java领域最常用的日志框架之一,可以灵活控制日志内容、输出位置(控制台、文件、数据库等&a…...
无人机遥控器的亮度 和 两个工作频率
工作频率 2.4000-2.4835 GHz , 5.725-5.850 GHz 1.这是一个无人机的遥控器的两个工作频率,为什么会有两个工作频率? 无人机的遥控器采用双频段设计(2.4GHz 和 5.8GHz),主要是为了解决以下问题并优化性…...
【Linux】命令行参数 | 环境变量(四)
目录 前言: 一、命令行参数: 1.main函数参数 2.为什么有它? 二、环境变量: 1.main函数第三个参数 2.查看shell本身环境变量 3.PATH环境变量 4.修改PATH环境变量配置文件 5.HOME环境变量 6.SHELL环境变量 7.PWD环境变…...
算法002——复写零
力扣——复写零点击即可跳转 这道题还是运用 双指针,我们从左往右开始,让 cur 0,dest 0,当我们循环时,会覆盖后面的值,所以从左到右无法实现,我们运用 从右到左的方式。 以示例一数组为例,从…...
例子 DQN + CartPole: 深入思考一下,强化学习确实是一场智能冒险之旅!
强化学习的概念 在技术人员眼里,深度学习、强化学习,或者是大模型,都只是一些算法。无论是简单,还是复杂,我们都是平静的看待。当商业元素日益渗透进技术领域,人人言必称大模型的时候。技术人该反思一下&a…...
java 实现xxl-job定时任务自动注册到调度中心
xxl-job 自动注册(执行器和任务) 前言 xxl-job是一个功能强大、简单易用、高可用且可扩展性强的分布式定时任务框架/分布式任务调度平台。它适用于各种需要定时任务调度的场景,并可根据业务需求进行灵活配置和扩展。 xxl-job简介 xxl-job是一个开源的分布式定时任务框架,…...
esp32串口通信
1、线路图 2、打开电脑的串口终端 3、eps32通过串口往电脑的串口终端输出信息: from machine import UART, Pin import time# 初始化UART0,波特率设置为115200 uart UART(0, baudrate115200, tx1, rx3)# 主循环 while True:# 要发送的消息#某些串口终…...
蓝桥杯备赛-前缀和-可获得的最小取值
问题描述 妮妮学姐手头有一个长度为 nn 的数组 aa,她想进行 kk 次操作来取出数组中的元素。每次操作必须选择以下两种操作之一: 取出数组中的最大元素。取出数组中的最小元素和次小元素。 妮妮学姐希望在进行完 kk 次操作后,取出的数的和最…...
UniApp 中封装 HTTP 请求与 Token 管理(附Demo)
目录 1. 基本知识2. Demo3. 拓展 1. 基本知识 从实战代码中学习,上述实战代码来源:芋道源码/yudao-mall-uniapp 该代码中,通过自定义 request 函数对 HTTP 请求进行了统一管理,并且结合了 Token 认证机制 请求封装原理ÿ…...
边缘计算+多模态感知:户外监控核心技术解析与工程部署实践!户外摄像头监控哪种好?户外摄像头监控十大品牌!格行视精灵VS海康威视VS大华横评!
一、核心参数解析与选型逻辑 1.环境适应性设计 极端天气防护:优先选择IP66/67防护等级的设备,例如格行视精灵通过IP67防水防尘设计可应对暴雨、沙尘暴等复杂环境,其密封轴承结构可有效防止水汽侵蚀内部电路。 温度耐受范围:北方…...
Spring项目-抽奖系统(实操项目)(ONE)
^__^ (oo)\______ (__)\ )\/\ ||----w | || || 一:前言: 随着互联网技术的快速发展,线上营销活动已成为企业吸引用户、…...
STM32-智能小车项目
项目框图 ST-link接线 实物图: 正面: 反面: 相关内容 使用L9110S电机模块 电机驱动模块L9110S详解 | 良许嵌入式 测速模块 语音模块SU-03T 网站:智能公元/AI产品零代码平台 一、让小车动起来 新建文件夹智能小车项目 在里面…...
Python:字符串常见操作
find(子字符串,开始位置下标,结束位置下标) 注意:开始位置和结束位置下标可以省略,表示在整个字符串中查找 stasdfghjkl print(st.find(a))#输出结果为0,表明a在第一个位置默认从零开始,找不到则返回-1 …...
Redis 哈希(Hash)
Redis 哈希(Hash) 概述 Redis 哈希(Hash)是一种特殊的键值对类型,它允许存储结构化的数据,例如一个对象或记录。每个哈希值可以包含多个字段,每个字段又可以存储一个字符串值。这使得Redis哈希非常适合用于存储对象的…...
Windows对比MacOS
Windows对比MacOS 文章目录 Windows对比MacOS1-环境变量1-Windows添加环境变量示例步骤 1:打开环境变量设置窗口步骤 2:添加系统环境变量 2-Mac 系统添加环境变量示例步骤 1:打开终端步骤 2:编辑环境变量配置文件步骤 3࿱…...
react 路由跳转的几种方式
在 React 中,路由跳转通常通过 react-router-dom(或类似的路由库)来实现。以下是几种常见的路由跳转方式: 1. 使用 <Link> 组件 <Link> 是最简单的路由跳转方式,它会生成一个 <a> 标签,…...
2.你有什么绝活儿?—Java能做什么?
1、Java的绝活儿:要问Java有什么绝活,我觉得它应该算是一位魔法师,会的绝活儿有很多,要说最能拿得出手的当属以下三个。 1.1 平台无关性:Java可以在任何地方施展魔法,无论是Windows、Linux还是Mac…...
2025年2月文章一览
2025年2月编程人总共更新了17篇文章: 1.2025年1月文章一览 2.《Operating System Concepts》阅读笔记:p2-p8 3.《Operating System Concepts》阅读笔记:p9-p12 4.《Operating System Concepts》阅读笔记:p13-p16 5.《Operati…...
C++ | 面向对象 | 类
👻类 👾语法格式 class className{Access specifiers: // 访问权限DataType variable; // 变量returnType functions() { } // 方法 };👾访问权限 class className {public:// 公有成员protected:// 受保护成员private:// 私有成员 }…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
