【重走C++学习之路】16、AVL树
目录
一、概念
二、AVL树的模拟实现
2.1 AVL树节点定义
2.2 AVL树的基本结构
2.3 AVL树的插入
1. 插入步骤
2. 调节平衡因子
3. 旋转处理
4. 开始插入
2.4 AVL树的查找
2.5 AVL树的删除
1. 删除步骤
2. 调节平衡因子
3. 旋转处理
4. 开始删除
结语
一、概念
二叉搜索树查找效率为O(logN),但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率变为O(N),效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新节点后,如果能保证每个节点的左右子树高度之差的绝对值不超过1(超过则需要对树中的节点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树具有以下特点:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/01)
平衡因子的取值由自己规定,在这里为了模拟实现,规定左子树的高度在根结点中为负值,右子树的高度在根结点中为正值,即平衡因子= 右子树高度 - 左子树高度。

二、AVL树的模拟实现
2.1 AVL树节点定义
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left; // 该节点的左孩子AVLTreeNode<K, V>* _right; // 该节点的右孩子AVLTreeNode<K, V>* _parent; // 该节点的父结点pair<K, V> _kv;int _bf; // 该节点的平衡因子AVLTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}
};
2.2 AVL树的基本结构
template <class K, class V>
class AVLTree
{typedef AVTreeNode<K, V> Node;
public:bool Insert(const std::pair<K, V>& kv);bool Find(const K& key);bool Erase(const K& key);private:Node* _root = nullptr;
};
2.3 AVL树的插入
1. 插入步骤
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
2. 调节平衡因子
插入节点后,会影响该节点到根节点这一条路径上节点的平衡因子,因此需要进行迭代,将这一条路径上的平衡因子更新。
步骤:
第一步: 对插入的节点的父节点进行更新,会发生两种情况:
- 如果插入的是根节点,则无父节点,不用更新。
- 如果插入的是父节点的左节点,则父节点的平衡因子-1;如果插入的是父节点的右节点,则父节点的平衡因子+1。
第二步: 父节点的平衡因子在插入节点后会有三种变化,也对应着三种措施:
- 父节点的平衡因子变为0,则说明在插入前两颗子树高度相差1,插入后整体的高度没有发生变化,不需要向上继续调整。
- 父节点的平衡因子变为-1或1,则说明在插入前两颗子树高度相等,插入后整体的高度+1,需要对父节点的父节点的平衡因子进行更新,这样就回到了步骤一。
- 父节点的平衡因子变为-2或2,则说明在插入前两个子树高度相差1,且插入的节点为高度较高的那颗子树的叶子节点,这导致了高度相差变为2,需要进行旋转处理,降低树的高度,从而达到平衡整颗树的要求。
图解:
第一种情况:

第二种情况:

可以看到这里在更新平衡因子的时候,只会影响节点到根节点路径上的节点的平衡因子,其它的节点不需要考虑。
第三种情况:

更新平衡因子的时候,发现不满足规则后,直接停止更新,转而进行旋转处理。
3. 旋转处理
旋转有四种情况:
第一种情况:右单旋
新节点插入到较高左子树的左侧,左侧的高度变高需要将右边的调整下来,因此叫做右单旋。
具体操作:
让subL的右孩子成为parent的左孩子,然后让parent成为subL的右孩子,最后把两个节点的平衡因子修改为0。
图解:

实现代码:
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;// 1.先让把subL的右边作为parent的左边parent->_left = subLR;// 2.如果subLR不为空,就让subLR的父指针指向parentif (subLR) subLR->_parent = parent;// 3.记录parent的父节点的位置,然后把parent作为subL的右边Node* pParent = parent->_parent;subL->_right = parent;// 4.parent的父指针指向subLparent->_parent = subL;// 5.如果ppNode为空,说明subR现在是根节点,就让subL的父指针指向nullptr// 不是根节点就把subL的父指针指向parent的父节点,parent的父节点(左或右)指向subLif (pParent == nullptr){// 更新根节点_root = subL;subL->_parent = nullptr;}else{// 判断parent是pparent的左还是右if (pParent->_left == parent)pParent->_left = subL;elsepParent->_right = subL;subL->_parent = pParent;}// 6.把parent和subL的平衡因子更新为0subL->_bf = parent->_bf = 0;
}
第二种情况:左单旋
新节点插入到较高右子树的右侧,右侧的高度变高需要将左边的调整下来,因此叫做右单旋。
具体操作:
让subR的左孩子成为parent的右孩子,然后让parent成为subR的左孩子,最后把两个节点的平衡因子修改为0。
图解:

实现代码:
void RotateL(Node* parent)
{ Node* subR = parent->_right;Node* subRL = subR->_left;// 1.先让把subR的左边作为parent的右边parent->_right = subRL;// 2.如果subRL不为空,就让subRL的父指针指向parentif (subRL) subRL->_parent = parent;// 3.先记录parent的父节点的位置,然后把parent作为subR的左边 Node* pParent = parent->_parent;subR->_left = parent;// 4.parent的父指针指向subRparent->_parent = subR;// 5.如果ppNode为空,说明subR现在是根节点,就让subR的父指针指向nullptr// 不是根节点就把subR的父指针指向parent的父节点,parent的父节点(左或右)指向subRif (pParent == nullptr){// 更新根节点_root = subR;subR->_parent = nullptr;}else{// 判断parent是ppNode的左还是右if (pParent->_left == parent)pParent->_left = subR;elsepParent->_right = subR;subR->_parent = pParent;}// 6.把parent和subR的平衡因子更新为0subR->_bf = parent->_bf = 0;
}
第三种情况:左右双旋
新节点插入在较高左子树右侧,这里和第一种情况的区别就是前者是直线,后者是折线
具体操作:
先对subL进行一个左单旋,然后对parent进行右单旋,修改平衡因子,有三种改法。三个节点从左至右的三个节点一次是:subL、subLR和parent。
如果subLR的平衡因子为0,就将它们依次改为0,0, 0;
如果subLR的平衡因子为1,就将它们依次改为-1,0, 0;
如果subLR的平衡因子为-1,就将它们依次改为0,0, 1。
图解:(这里只画出了一种情况,剩下的类似)

实现代码:
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;// 保留subLR的平衡因子的值,方便知道新插入的节点是在subLR的左子树还是右子树// 旋转 先对subL进行左旋转,再对parent进行右旋转RotateL(subL);RotateR(parent);// 从左到右 subL subLR parentif (bf == -1)// subLR的左子树 bf: 0 0 1{subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 1)// subLR的右子树 bf: -1 0 0{subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}
}
第四种情况:右左单旋
新节点插入在较高右子树左侧,这里和第二种情况的区别就是前者是直线,后者是折线
具体操作:
先对subR进行一个右单旋,然后对parent进行左单旋,修改平衡因子,有三种改法。三个节点从左至右的三个节点一次是:parent、subRL和subR。
如果subRL的平衡因子为0,就将它们依次改为0,0, 0;
如果subRL的平衡因子为1,就将它们依次改为-1,0, 0;
如果subRL的平衡因子为-1,就将它们依次改为0,0, 1。
图解:(这里只画出了一种情况,剩下的类似)

实现代码:
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;// 保留subRL的平衡因子的值,方便知道新插入的节点是在subRL的左子树还是右子树// 旋转 先对subR进行右旋转,再对parent进行左旋转RotateR(subR);RotateL(parent);// 从左到右 parent subRL subRif (bf == -1)// subRL的左子树 bf: 0 0 1{parent->_bf = 0;subRL->_bf = 0;subR->_bf = 1;}else if (bf == 1)// subRL的右子树 bf: -1 0 0{parent->_bf = -1;subRL->_bf = 0;subR->_bf = 0;}else if (bf == 0){parent->_bf = 0;subRL->_bf = 0;subR->_bf = 0;}
}
4. 开始插入
bool Insert(const pair<K, V>& kv)
{// 先按照二叉搜索数一样插入元素// 无节点是插入if (_root == nullptr){_root = new Node(kv);return true;}// 有节点时插入Node* parent = nullptr;Node* cur = _root;while (cur){parent = cur;// 小于往左走if (kv.first < cur->_kv.first){cur = cur->_left;}// 大于往右走else if (kv.first > cur->_kv.first){cur = cur->_right;}else{// 找到了,就返回falsereturn false;}}cur = new Node(kv);// 判断cur应该插在parent的左还是右 // 小于在左,大于在右 if (cur->_kv.first < parent->_kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}// 更新parent的平衡因子// 节点的插入只会影响cur的祖先的平衡因子(不是所有的,是一部分,分情况)while (parent){// 更新parent的平衡因子// cur在parent的左,parent->_bf--// cur在parent的右,parent->_bf++if (cur == parent->_left)parent->_bf--;elseparent->_bf++;// bf 可能为 -2、-1、0、1、2if (parent->_bf == 0){// 对上层无影响,退出break;}else if (parent->_bf == -1 || parent->_bf == 1){// 对上层有影响,迭代更新cur = parent;parent = parent->_parent;}else{// 平衡树出现了问题,需要调整// 1.右边高,左旋转调整if (parent->_bf == 2){// 如果是一条直线==>左旋转即可// 如果是一条折线==>右左旋转if (cur->_bf == 1)RotateL(parent);else if (cur->_bf == -1)RotateRL(parent);}// 2.左边高,右旋转调整else if (parent->_bf == -2){// 如果是一条直线==>右旋转即可// 如果是一条折线==>左右旋转if (cur->_bf == -1)RotateR(parent);else if (cur->_bf == 1)RotateLR(parent);}// 调整后是平衡树,bf为0,不需要调整了break;}}return bool;
}
2.4 AVL树的查找
与二叉搜索树的过程一致,这里就不多解释了,直接上代码:
bool Find(const K& key)
{if (_root == nullptr)return false;Node* cur = _root;while (cur){// 小于往左走if (key < cur->_kv.first){cur = cur->_left;}// 大于往右走else if (key > cur->_kv.first){cur = cur->_right;}else{// 找到了return true;}}return false;
}
2.5 AVL树的删除
1. 删除步骤
- 我们先按照二叉搜索树树删除节点的方式,删除节点。
- 根据对应删除情况更新平衡因子,更新平衡因子的方法与插入的更新方法是相反的。
2. 调节平衡因子
步骤:
第一步:
- 删除节点后,如果删除的节点为根节点,就结束。
- 如果删除节点是父节点的左孩子,那么父节点的平衡因子加1;删除节点是父节点的右孩子,那么父节点的平衡因子减1。
第二步:父节点的平衡因子在插入节点后会有三种变化,也对应着三种措施:
- 父节点的平衡因子变为0,则说明删除前父亲的平衡因子为1或-1,多出一个左节点或右节点,删除节点后,左右高度相等,整体高度减1,对上层有影响,需要继续调节。
- 父节点的平衡因子变为-1或1,则说明删除前父亲的平衡因子为0,左右高度相等,删除节点后,少了一个左节点或右节点,但是整体高度不变,对上层无影响,不需要继续调节。
- 父节点的平衡因子变为-2或2,则说明删除前父亲的平衡因子为-1或1,多了一个左节点或一个右节点,删除了一个右节点或左节点,此时多了两个左节点和右节点,这棵子树一边已经被拉高了,此时这棵子树不平衡了,需要旋转处理。
3. 旋转处理
这里的旋转与插入时旋转的没有区别,就不详细解答了,直接上代码。
4. 开始删除
bool Erase(const K& key)
{if (_root == nullptr)return false;// 有节点时插入Node* parent = nullptr;Node* cur = _root;while (cur){// 小于往左走if (key < cur->_kv.first){parent = cur;cur = cur->_left;}// 大于往右走else if (key > cur->_kv.first){parent = cur;cur = cur->_right;}else{// 找到了// 1.左边为空,把parent指向cur的右// 2.右边为空,把parent指向cur的左// 3.左右都不为空,用右子树的最左边的节点的值替换要删除的节点,然后转换为用1的情况删除该节点if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;delete cur;break;}else{if (parent->_left == cur){parent->_left = cur->_right;parent->_bf++;}else{parent->_right = cur->_right;parent->_bf--;}}if (parent->_bf != -1 && parent->_bf != 1) UpdateBf(parent);delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;delete cur;break;}else{if (parent->_left == cur){parent->_left = cur->_left;parent->_bf++;}else{parent->_right = cur->_left;parent->_bf--;}}if (parent->_bf != -1 && parent->_bf != 1) UpdateBf(parent);delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;// 先去右子树while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;// 一种往左走}cur->_kv = rightMin->_kv;// 替代删除// 删除minNode 第一种情况:左节点为空if (rightMinParent->_left == rightMin){rightMinParent->_left = rightMin->_right;rightMinParent->_bf++;}else{rightMinParent->_right = rightMin->_right;rightMinParent->_bf--;}if (rightMinParent->_bf != -1 && rightMinParent->_bf != 1) UpdateBf(rightMinParent);delete rightMin;}return true;}}return false;
}void UpdateBf(Node* parent)
{if (parent == nullptr)return;Node* cur = parent;goto first;while (parent){// 更新parent的平衡因子// cur在parent的左,parent->_bf++// cur在parent的右,parent->_bf--if (cur == parent->_left)parent->_bf++;elseparent->_bf--;// bf 可能为 -2、-1、0、1、2first:if (parent->_bf == 0){// 对上层有影响,迭代更新// 如果parent是根节点就结束if (parent->_parent == nullptr)break;cur = parent;parent = parent->_parent;}else if (parent->_bf == -1 || parent->_bf == 1){// 对上层无影响,退出break;}else{// 平衡树出现了问题,需要调整// 1.右边高,左旋转调整if (parent->_bf == 2){// 如果是一条直线==>左旋转即可// 如果是一条折线==>右左旋转if (parent->_right->_bf == 1){RotateL(parent);cur = parent->_parent;parent = cur->_parent;continue;}else if (parent->_right->_bf == -1)// 调整后 p sL s 如果sL是1或-1可以退出 {Node* s = parent->_right;Node* sL = s->_left;RotateRL(parent);// 不平衡向上调整if (sL->_bf != 1 && sL->_bf != -1){cur = sL;parent = cur->_parent;continue;}}else if (parent->_right->_bf == 0) // 平衡因子修改{RotateL(parent);parent->_bf = 1;parent->_parent->_bf = -1;}}// 2.左边高,右旋转调整else if (parent->_bf == -2){// 如果是一条直线==>右旋转即可// 如果是一条折线==>左右旋转if (parent->_left->_bf == -1){RotateR(parent);cur = parent->_parent;parent = cur->_parent;continue; //parent不是-1或1就继续}else if (parent->_left->_bf == 1)// 调整后 s sR p 如果sL是1或-1可以退出{Node* s = parent->_left;Node* sR = s->_right;RotateLR(parent);// 不平衡向上调整 为0时如果parent为根//if (sR->_bf != 1 && sR->_bf != -1){cur = sR;parent = cur->_parent;continue;}}else if (parent->_left->_bf == 0) // 平衡因子修改{RotateR(parent);parent->_parent->_bf = 1;parent->_bf = -1;}}// 调整后是平衡树,bf为1或-1,不需要调整了break;}}
}
结语
上面这些是AVL树的大致内容,其中旋转是有些难的地方,但是面试会考察,需要着重掌握,而删除是二叉搜索树的删除加上旋转的叠加,难度更上一层了,这里如果没能理解,可以自己画一画图,并且配合着插入的图来分析,应该会有所帮助。
下一篇将会介绍二叉搜索树的另一种改良:红黑树,有兴趣的朋友可以关注一下。
相关文章:
【重走C++学习之路】16、AVL树
目录 一、概念 二、AVL树的模拟实现 2.1 AVL树节点定义 2.2 AVL树的基本结构 2.3 AVL树的插入 1. 插入步骤 2. 调节平衡因子 3. 旋转处理 4. 开始插入 2.4 AVL树的查找 2.5 AVL树的删除 1. 删除步骤 2. 调节平衡因子 3. 旋转处理 4. 开始删除 结语 一、概念 …...
NumPy进阶:广播机制、高级索引与通用函数详解
目录 一、广播机制:不同形状数组间的运算 1. 概念 2. 广播规则 3. 实例 二、高级索引:布尔索引与花式索引 1. 布尔索引 (1)创建布尔索引 (2)布尔索引的应用 2. 花式索引 (1࿰…...
597页PPT丨流程合集:流程梳理方法、流程现状分析,流程管理规范及应用,流程绩效的管理,流程实施与优化,流程责任人的角色认知等
流程梳理是通过系统化分析优化业务流程的管理方法,其核心包含四大步骤:①目标确认,明确业务痛点和改进方向;②现状分析,通过流程图、价值流图还原现有流程全貌,识别冗余环节和瓶颈节点;③优化设…...
[密码学基础]GMT 0029-2014签名验签服务器技术规范深度解析
GMT 0029-2014签名验签服务器技术规范深度解析 引言 在数字化转型和网络安全需求激增的背景下,密码技术成为保障数据完整性与身份认证的核心手段。中国密码管理局发布的GMT 0029-2014《签名验签服务器技术规范》,为签名验签服务器的设计、开发与部署提…...
Kinibi-610a:面向芯片厂商与设备制造商的TEE升级详解
安全之安全(security)博客目录导读 目录 一、TEE内存管理革新 二、TA加载架构优化 三、系统日志(syslog)集成 四、加密日志支持 五、工具链升级至Python3 六、总结与展望 七、参考资料 Trustonic最新发布的可信执行环境(TEE)Kinibi-610a,在前代Kinibi-600多平台支…...
来啦,烫,查询达梦表占用空间
想象一下oracle,可以查dba_segments,但是这个不可靠(达梦官方连说明书都没有) 先拼接一个sql set lineshow off SELECT SELECT ||||OWNER|||| AS OWNER,||||TABLE_NAME|||| AS TABLE_NAME,TABLE_USED_SPACE(||||OWNER||||,||||T…...
vue3:十一、主页面布局(修改左侧导航条的样式)
一、样式 1、初始样式 2、 左侧导航栏搭建完成样式 二、实现 1、设置左侧导航栏底色 (1)去掉顶部和左侧导航栏的底色 初始页面效果 顶部与左侧底色样式 将代码中与顶部与左侧的样式删掉 移除后页面效果 加入设定背景色 #f4f6f9 加入底色后颜色展示 (2)去除菜单项底色 初…...
开发网页程序时预览时遇到跨域问题解决方法
CocosCreator 开发h5游戏要用接口、开发html程序网页程序在chrome中预览时都会遇到跨域问题,怎么办? 网上有很多方法,主要是通过服务器端去配置,但那个相对来说消弱安全问题,这个不建议,因为是开发,个人行业,我们知道问题所以,简单点就主要是通过chrome的参数来禁用: 关闭 Ch…...
Sentinel源码—7.参数限流和注解的实现二
大纲 1.参数限流的原理和源码 2.SentinelResource注解的使用和实现 2.SentinelResource注解的使用和实现 (1)SentinelResource注解的使用 (2)SentinelResource注解和实现 (1)SentinelResource注解的使用 一.引入Sentinel Spring Boot Starter依赖 <dependency><…...
操作系统原理简要介绍
文章目录 计算机启动的底层流程(从裸机到操作系统)内核用户态与内核态内核分类 进程与线程:操作系统的 “执行者”内存管理:数据的“存储与调度”文件系统:数据的“组织与持久化”设备驱动:硬件的“翻译官”…...
QML ShaderEffect(着色器效果)组件
ShaderEffect 是 QML 中用于实现自定义着色器效果的组件,允许开发者使用 GLSL 着色器语言创建图形效果。 核心属性 基本属性 属性类型默认值说明fragmentShaderstring""片段着色器代码vertexShaderstring""顶点着色器代码blendingbooltrue是…...
2.6 递归
递归 特性: >.一递一归 >.终止条件 一般为:0 1 -1 #测试函数的返回值为函数 def test_recursion():return test_recursion() print(test_recursion()) RecursionError: maximum recursion depth exceeded #案例:计算 …...
麒麟系统网络连接问题排查
麒麟系统网络连接有红色叹号,不能上外网 了。 首先执行 ping -c4 8.8.8.8 和 nc -zv 8.8.8.8 53,如果 都能正常通信,说明你的网络可以访问公共 DNS 服务器(如 Google DNS 8.8.8.8),但域名解析仍然失败,可能是 DNS 解析配置问题 或 系统 DNS 缓存/代理干扰。以下是进一步…...
opencv(双线性插值原理)
双线性插值是一种图像缩放、旋转或平移时进行像素值估计的插值方法。当需要对图像进行变换时,特别是尺寸变化时,原始图像的某些像素坐标可能不再是新图像中的整数位置,这时就需要使用插值算法来确定这些非整数坐标的像素值。 双线性插值的工…...
从信号处理角度理解图像处理的滤波函数
目录 1、预备知识 1.1 什么是LTI系统? 1.1.1 首先来看什么是线性系统,前提我们要了解什么是齐次性和叠加性。...
echarts模板化开发,简易版配置大屏组件-根据配置文件输出图形和模板(vue2+echarts5.0)
实现结果 项目结构 根据我的目录和代码 复制到项目中 echartsTemplate-echarts图形 pie实例 <template><div :id"echartsId"></div> </template> <script> export default {name: ,components: {},mixins: [],props: [echartsId,…...
从人工到智能:外呼系统如何重构企业效率新生态
在数字化转型的浪潮中,智能外呼系统正从边缘辅助工具演变为企业效率革命的核心引擎。根据Gartner最新调研数据,部署AI外呼系统的企业客服效率平均提升68%,销售线索转化率增长42%。但在这场技术驱动的变革中,真正决定成败的往往不是…...
HTTP 2.0 和 3.0 的区别
HTTP 2.0 和 3.0 的核心区别体现在底层协议设计、性能优化和网络适应性上,以下是具体对比: 一、核心区别对比 特性HTTP 2.0HTTP 3.0(HTTP/3)底层传输协议TCPUDP(基于 QUIC 协议)队头阻塞(TCP …...
Qt项目——Tcp网络调试助手服务端与客户端
目录 前言结果预览工程文件源代码一、开发流程二、Tcp协议三、Socket四、Tcp服务器的关键流程五、Tcp客户端的关键流程六、Tcp服务端核心代码七、客户端核心代码总结 前言 这期要运用到计算机网络的知识,要搞清楚Tcp协议,学习QTcpServer ,学…...
4.21 从0开始配置spark-local模式
首先准备好安装包 然后使用命令解压 使用source /etc/profile命令让环境变量生效 输入命令 spark-submit --class org.apache.spark.examples.SparkPi --master local[2] /opt/module/spark-local/examples/jars/spark-examples_2.12-3.1.1.jar 10 即在spark运行了第一个程序…...
chili3d调试笔记3 加入c++ 大模型对话方法 cmakelists精读
加入 #include <emscripten/bind.h> #include <emscripten/val.h> #include <nlohmann/json.hpp> 怎么加包 函数直接用emscripten::function,如: emscripten::function("send_to_llm", &send_to_llm); set (CMAKE_C…...
go语言八股文
1.go语言的接口是怎么实现 接口(interface)是一种类型,它定义了一组方法的集合。任何类型只要实现了接口中定义的所有方法,就被认为实现了该接口。 代码的实现 package mainimport "fmt"// 定义接口 type Shape inte…...
C++——多态、抽象类和接口
目录 多态的基本概念 如何实现多态 在C中,派生类对象可以被当作基类对象使用 编程示例 关键概念总结 抽象类 一、抽象类的定义 基本语法 二、抽象类的核心特性 1. 不能直接实例化 2. 派生类必须实现所有纯虚函数才能成为具体类 3. 可以包含普通成员函数和…...
【Java面试笔记:基础】3.谈谈final、finally、 finalize有什么不同?
1. final、finally、finalize 的区别 final: 用途:用于修饰类、方法和变量。 修饰类:表示该类不能被继承。 final class ImmutableClass { // 此类无法被其他类继承 }修饰方法:表示该方法不能被子类重写。 class Parent {fin…...
基于 DeepSeek大模型 开发AI应用的理论和实战书籍推荐,涵盖基础理论、模型架构、实战技巧及对比分析,并附表格总结
以下是基于 DeepSeek大模型 开发AI应用的理论和实战书籍推荐,涵盖基础理论、模型架构、实战技巧及对比分析,并附表格总结: 1. 推荐书籍及内容说明 (1) 《深度学习》(Deep Learning) 作者:Ian Goodfellow…...
从数字化到智能化,百度 SRE 数智免疫系统的演进和实践
1. 为什么 SRE 需要数智免疫系统? 2022 年 10 月,在 Gartner 公布的 2023 年十大战略技术趋势中提到了「数字免疫系统」的概念,旨在通过结合数据驱动的一系列手段来提高系统的弹性和稳定性。 在过去 2 年的时间里,百度基于该…...
[Git] Git Stash 命令详解
1. Git Stash 的基本概念 Git Stash 是一个用于暂存当前工作目录中更改的命令。当你正在处理一个功能分支,但突然需要切换到另一个分支进行紧急修复或查看其他工作时,Git Stash 就显得非常有用。它允许你将当前工作目录中的更改保存起来,以便…...
ArcGIS及其组件抛出 -- “Sorry, this application cannot run under a Virtual Machine.“
产生背景: 使用的是“破解版本”或“被套壳过”的非官方 ArcGIS 版本 破解版本作者为了防止: 被研究破解方式 被自动化抓包/提权/逆向 被企业环境中部署多机使用 通常会加入**“虚拟化环境检测阻断运行”机制** 原因解释: 说明你当前运…...
Python项目调用Java数据接口实现CRUD操作
Django Python项目调用Java数据接口实现CRUD操作:接口设计与实现指南 引言 在现代软件架构中,系统间的数据交互变得越来越重要。Python和Java作为两种流行的编程语言,在企业级应用中常常需要实现跨语言的数据交互。本报告将详细介绍如何在D…...
进阶篇 第 5 篇:现代预测方法 - Prophet 与机器学习特征工程
进阶篇 第 5 篇:现代预测方法 - Prophet 与机器学习特征工程 (图片来源: ThisIsEngineering RAEng on Pexels) 在前几篇中,我们深入研究了经典的时间序列统计模型,如 ETS 和强大的 SARIMA 家族。它们在理论上成熟且应用广泛,但有…...
