【进阶数据结构】平衡搜索二叉树 —— AVL树
🌈感谢阅读East-sunrise学习分享——[进阶数据结构]AVL树
博主水平有限,如有差错,欢迎斧正🙏感谢有你 码字不易,若有收获,期待你的点赞关注💙我们一起进步🚀
🌈我们上一篇博客分享了搜索二叉树,在文中也铺垫了搜索二叉树的一些结构局限性
而今天分享的一种特殊的搜索二叉树——AVL树,便是一种结构优异的搜索二叉树🎄那么我们就开始吧🚀🚀🚀
目录
- 一、AVL树的概念
- 二、AVL树结点的定义
- 三、AVL树的插入
- 四、AVL树的旋转
- 1.左单旋
- 2.右单旋
- 3.左右双旋
- 4.右左双旋
- 五、最终代码展示
一、AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
🎄一棵AVL树可以是一棵空树,或者是一棵具有以下性质的二叉搜索树
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1
这里的平衡因子是指:右子树高度-左子树高度⭕
注意:平衡因子只是博主分享的这种实现方法的一种自定义名字(不是必须的),除了使用平衡因子之外还有许多实现AVL树的方法
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(logN),搜索时间复杂度O(logN)
二、AVL树结点的定义
AVL树的结点我们定义了一个三叉链结构,便于后续的操作;并且在每个结点中都引入了平衡因子
template<class K, class V>
struct AVLTreeNode
{//存储键值对的pair类pair<K, V> _kv;//含有父节点的三叉链AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;//平衡因子int _bf;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};//AVL树
template<class K,class V>
struct AVLTree
{typedef AVLTreeNode<K, V> Node;
public://插入bool Insert(const pair<K, V>& kv){}private:Node* _root = nullptr;
};
三、AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点
- 调整平衡因子,若不平衡则需要旋转调整AVL树
⭕⭕当有新节点插入后我们就需要判断此时的树是否仍然平衡仍然是AVL树了
🚩插入后平衡因子的变化类型?
我们知道,假如平衡,则每个结点的平衡因子只有三种可能:-1,0,1
而插入新结点肯定会使得高度的变化,假如插入新节点后仍平衡,则父节点的平衡因子的变化有:
- 0 --> 1
- 0 --> -1
- 1 --> 0
- -1 --> 0
知道了平衡因子的变化情况后,又抛出了一个问题
🚩插入新节点影响父节点的平衡因子,那是否会影响祖先结点的平衡因子?
⭕最简单的情况就是插入了新节点,只影响了其父结点,只需更新父节点的平衡因子
插入新节点后,改变了其父结点(8)的子树高度,所以需要更新父节点的平衡因子,但是插入之后并不会改变其祖先结点的子树高度,所以不需要往上更新平衡因子
📌因此我们可以总结出:是否持续更新平衡因子,取决于其结点的子树高度是否变化
再结合一开始的平衡因子变化情况我们可以得出插入新结点后:
- parent -> _bf == 0 —— 说明之前parent -> _bf 是 1 或者 -1(一边高一边低)新节点刚好插入填上矮的那边,parent所在子树高度不变 —— 祖先的子树高度也不会变 —— 只需更新parent的平衡因子,不需要继续往上更新
- parent -> _bf == 1 或 -1 —— 说明之前parent -> _bf == 0(两边一样高)新结点插入使得parent所在子树的高度变得一高一低 —— 祖先的子树高度也产生变化 —— 更新parent的平衡因子之外,还需要继续往上更新祖先结点的平衡因子
- parent -> _bf == 2 或 -2 —— 说明本就一高一低的子树,插入新节点后造成更加不平衡,此时违反了AVL树的平衡规则 —— 就地处理 ——旋转调整
⭕最坏的情况就是插入了新节点,直接影响到了root根结点,所以需要持续更新到root根结点的平衡因子
💭更新结点的平衡因子时,假若我们需要持续向上更新平衡因子,一开始我们更新的是最下面的parent结点,更新后则可向上迭代,直到parent为空就停止
✏️代码实现
bool Insert(const pair<K, V>& kv){//空if (_root == nullptr){_root = new Node(kv);return true;}//非空Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}elsereturn false;}//插入cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//调整平衡因子while (parent){if (cur == parent->_right)parent->_bf++;elseparent->_bf--;if (parent->_bf == 0)break;else if (parent->_bf == -1 || parent->_bf == 1){cur = parent;parent = parent->_parent;}else if (parent->_bf == -2 || parent->_bf == 2){//旋转调整}elseassert(false);}return true;}
四、AVL树的旋转
🌏如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。
因此旋转的要求即是:
- 旋转后仍保持二叉搜索树的结构
- 旋转后整棵树保持平衡,平衡因子不超过1
而根据节点插入位置的不同,AVL树的旋转分为四种:
1.左单旋
1️⃣新节点插入较高右子树的右侧 —— 左单旋
此处我们给出左单旋过程的抽象图📌
我们发现,当parent的平衡因子是2,cur是1时,便进行左单旋 ——> 将cur的左子树给parent的右子树,然后将parent及其子树一整棵树变为cur的左子树
左单旋真就如此吗?不信我们可以画出具象图看看
✨当 h = 0
✨当 h = 1
✨当 h = 2
💭有的兄弟看到这就有疑问,为什么h = 2时,子树c一定就得是z的模样呢?
因为假如子树c是x或y的模样,插入新节点时并不会引发节点30的旋转,那样最多只是变成以节点60为parent的树进行左单旋,那就和h = 1是同样的情况了💤因此以上的情况,其实是笼盖了所有需要进行左单旋的子情况了🚩然后以上的情况可能是某棵树的子树
最后我们发现,所有需要进行左单旋的情况,最后的操作都是如一开始所说
✏️代码实现(对照图更清晰易懂)
void RotateL(Node* parent)
{Node* subR = parent->_right;//parent的右孩子Node* subRL = subR->_left;//parent的右孩子的左孩子//旋转后subR的左孩子作为parent的右孩子parent->_right = subRL;//subR的左孩子有可能为空也有可能存在//如果存在则需要更新父子关系if (subRL)subRL->_parent = parent;//subR的左孩子变为以parent为根的子树结构//同时更新父子关系subR->_left = parent;parent->_parent = subR;//parent也可能只是一棵子树的根,其pparent可能为空也可能存在Node* pparent = parent->_parent;if (pparent){//如果pparent不为空,则说明parent是一棵子树//可能是存在于其父节点的左子树or右子树if (parent == pparent->_left)pparent->_left = subR;elsepparent->_right = subR;subR->_parent = pparent;}else{//若pparent为空,则说明parent是整棵树的根节点//旋转后根节点已经换人了需要更新_root = subR;subR->_parent = nullptr;}//最后更新平衡因子parent->_bf = subR->_bf = 0;
}
📌看完以上的代码实现,发现旋转的代码实现起来也有许多细节需要注意啊…
因为旋转后也要保持一棵正常的树的结构,因此那些父子链接关系也需要正确更新
2.右单旋
2️⃣新节点插入较高左子树的左侧 - 右单旋
✏️实现及情况考虑可参考左单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pparent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (pparent){if (parent == pparent->_left)pparent->_left = subL;elsepparent->_right = subL;subL->_parent = pparent;}else{_root = subL;subL->_parent = nullptr;}subL->_bf = parent->_bf = 0;
}
3.左右双旋
3️⃣新节点插入较高左子树的右侧 - 先左单旋再右单旋
左右双旋我们可以复用上面的左单旋和右单旋的代码🚩但是需要注意的是,左右双旋完各个节点的平衡因子有不同的情况,正是因为左右双旋会因为新节点插入的位置不同而影响不同的旋转结果,因此我们总结出了以下三种情况:
-
h = 0 —— 节点60即是新插入节点
-
新节点插入在b
-
新节点插入在c
综上所述,当我们在实现左右双旋时的最后,可根据插入新节点后节点60的平衡因子大小,来确定不同的情况
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//更新平衡因子if (bf == 1) //新增在sublr右子树{parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1) //新增在sublr左子树{subL->_bf = 0;parent->_bf = 1;subLR->_bf = 0;}else //本身就是新增{parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}
}
4.右左双旋
4️⃣新节点插入较高右子树的左侧——先右单旋再左单旋
✏️实现及情况考虑可参考左右双旋
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if(bf == 0){parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}
}
五、最终代码展示
template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};template<class K,class V>
struct AVLTree
{typedef AVLTreeNode<K, V> Node;
public:AVLTree():_root(nullptr){}bool Insert(const pair<K, V>& kv){//空if (_root == nullptr){_root = new Node(kv);return true;}//非空Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}elsereturn false;}//插入cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//调整平衡因子while (parent){if (cur == parent->_right)parent->_bf++;elseparent->_bf--;if (parent->_bf == 0)break;else if (parent->_bf == -1 || parent->_bf == 1){cur = parent;parent = parent->_parent;}else if (parent->_bf == -2 || parent->_bf == 2){//旋转调整if (parent->_bf == 2 && cur->_bf == 1)RotateL(parent);else if (parent->_bf == -2 && cur->_bf == -1)RotateR(parent);else if (parent->_bf == -2 && cur->_bf == 1)RotateLR(parent);else if (parent->_bf == 2 && cur->_bf == -1)RotateRL(parent);elseassert(false);break;}elseassert(false);}return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* pparent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (pparent){if (parent == pparent->_left)pparent->_left = subR;elsepparent->_right = subR;subR->_parent = pparent;}else{_root = subR;subR->_parent = nullptr;}parent->_bf = subR->_bf = 0;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pparent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (pparent){if (parent == pparent->_left)pparent->_left = subL;elsepparent->_right = subL;subL->_parent = pparent;}else{_root = subL;subL->_parent = nullptr;}subL->_bf = parent->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 1) //新增在sublr右子树{parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1) //新增在sublr左子树{subL->_bf = 0;parent->_bf = 1;subLR->_bf = 0;}else if (bf == 0) //本身就是新增{parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if(bf == 0){parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);}}void Inorder(){_Inorder(_root);}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_Inorder(root->_right);}int Height(Node* root){if (root == nullptr)return 0;int hl = Height(root->_left);int hr = Height(root->_right);return hl > hr ? hl + 1 : hr + 1;}bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);if (rightHeight - leftHeight != root->_bf){cout << "平衡因子异常" << endl;return false;}return abs(rightHeight - leftHeight) < 2&& IsBalance(root->_left)&& IsBalance(root->_right);}private:Node* _root = nullptr;
};
🌈🌈写在最后 我们今天的学习分享之旅就到此结束了
🎈感谢能耐心地阅读到此
🎈码字不易,感谢三连
🎈关注博主,我们一起学习、一起进步
相关文章:

【进阶数据结构】平衡搜索二叉树 —— AVL树
🌈感谢阅读East-sunrise学习分享——[进阶数据结构]AVL树 博主水平有限,如有差错,欢迎斧正🙏感谢有你 码字不易,若有收获,期待你的点赞关注💙我们一起进步🚀 🌈我们上一篇…...
ROS使用(5)action学习
action消息的构建 首先进行功能包的创建 mkdir -p ros2_ws/src cd ros2_ws/src ros2 pkg create action_tutorials_interfaces action消息的类型 # Request --- # Result --- # Feedback 动作定义由三个消息定义组成,以---分隔。 从动作客户机向动作服务器发送…...
2023前端面试题集(含答案)之HTML+CSS篇(一)
在又到了金三银四的招聘季,不管你是刚入行的小白,亦或是混迹职场的老鸟,还在为面试前端工程师时不知道面试官要问什么怎么回答而苦恼吗?为了帮助你获得面试官的青睐,顺利通过面试,跳槽进入大厂,…...
设计模式2 - 观察者模式
定义: 观察者模式又叫发布订阅模式,它定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。 组成: Subject(通知者/被观察者)&#…...
ini配置文件
ini配置文件 ini文件是initialization file的缩写,即初始化文件,是widows系统配置文件所采用的存储格式。 文件扩展名: .ini ini配置文件的后缀名也不一定必须是.ini, 也可以是.cfg, .conf或者是.txt ini文件格式 ini配置文件由参数, 节, 注解组成 参…...
蓝桥杯备赛经验 pythonA组(非科班选手)
个人2022 CA组江苏省一等奖,决赛成绩不理想,没有拿到一二等奖,但是因为自己是非科班的学生,所以能拿到这样的成绩自己其实也应该知足了 题外话: 很多ACMer嘲笑蓝桥杯非常水,但是据我观察CA组决赛一等奖获奖…...

C++实现通讯录管理系统
通讯录是一个可以记录亲人、好友信息的工具,本博客借助黑马程序员的项目进行修改,利用C实现一个通讯录管理系统,旨在复习C的语法。 一、系统需求 系统需要实现的功能如下: 添加联系人∶向通讯录中添加新人,信息包括…...

开关电源Y电容放置的位置
Y电容,是我们工程师做开关电源设计时都要接触到的一个非常关键的元器件,它对EMI的贡献是相当的大的,但是它是一个较难把控的元器件,原理上并没有那么直观易懂,在EMI传播路径中需要联系到很多的寄生参数才能够去分析。 …...

二叉树的最小深度——递归法、迭代法
1题目给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。说明:叶子节点是指没有子节点的节点。示例 1:输入:root [3,9,20,null,null,15,7]输出:2示例 2:输入&…...
Vue中常使用的三种刷新页面的方式
一、通过js原始方法刷新 缺点: 出现闪白 目录 一、通过js原始方法刷新 二、通过Vue自带的路由进行跳转 三、通过在APP页面进行demo进行刷新(推荐) 1.vue2写法 2. vue3.2写法 <template><div><div class"header"><button clic…...

【Shell】脚本
Shell脚本脚本格式第一个Shell脚本:hello.sh脚本常用执行方式1. bash或sh脚本的相对路径或绝对路径2. 输入脚本的绝对路径或相对路径3. 在脚本的路径前加上.或者source脚本格式 脚本以#!/bin/bash开头(指定解析器) #! 是一个约定的标记&…...
Mybatis的多表操作
1.Mybatis多表查询 1.1一对一查询 1.一对一查询的模型 用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户 一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户2.创建Order和User实体public class…...
【JVM】字节码指令全解
文章目录 入门案例原始 java 代码编译后的字节码文件常量池载入运行时常量池方法字节码载入方法区main 线程开始运行,分配栈帧内存执行引擎开始执行字节码bipush 10istore_1ldc #3istore_2iload_1iload_2iaddistore_3getstatic #4iload_3invokevirtual #5return条件判断指令循…...

【精品】华为认证数通HCIA+HCIP题库分享(含答案解析)
嗨~大家好久不见,我是薄荷学姐,随着华为业务也全球领域的迅猛发展,越来越多人开始重视华为认证的重要性。今天给大家分享一下去年8月份的题库,基本都是一样,希望可以帮助到大家哈想要通过华为认证,除了进行…...
Qt cmake 资源文件的加载
Qt cmake 资源文件的加载概述qt_add_resourcesqt5_add_resourcesqt6_add_resources是否需要加载qrc文件需要加载qrc的情况不需要加载qrc的情况C 代码加载示例加载PNG加载CSS文件加载qrc文件Qt6相对于Qt5的一些变化Qt6和Qt5在加载资源文件方面的区别主要集中在两个方面ÿ…...

【链表OJ题(九)】环形链表延伸问题以及相关OJ题
环形链表OJ题 1. 环形链表 链接:141. 环形链表 描述: 给你一个链表的头节点 head ,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环&…...

【C++初阶】四、类和对象(下)
文章目录一、再谈构造函数构造函数体赋值初始化列表explicit关键字二、Static成员引入- 计算类中创建了多少个类对象概念特性静态成员函数的访问三、友元友元函数友元类四、内部类五、匿名对象六、拷贝对象时的一些编译器优化一、再谈构造函数 构造函数体赋值 在创建对象时&a…...

IDEA maven没有Import Maven projects automatically解决办法
去年装了个 IDEA2022版本 配置maven时我才发现是个大坑 他没有import Maven projects automatically配置项 当时看到我人都麻了 然后项目呢 用了依赖 这东西还不会自动下依赖 整的我那是相当难受 还在最后还是找到了解决办法 我们在配置文件上点击右键 然后鼠标选择如下图选项…...

Java实习生------MySQL10道面试题打卡
今日语录:“没有执行力,就没有竞争力 ”🌹 参考资料:图解MySQL、MySQL面试题 1、事务有哪些特性? 原子性: 一个事务中的所有操作,要么全部完成,要么全部不完成,不会出现…...
帆软报表设计器 数据集之数据库查询
当点击数据库查询时,调用TableDataTreePane的 public void actionPerformed(ActionEvent var1) {TableDataTreePane.this.dgEdit(this.getTableDataInstance().creatTableDataPane(), TableDataTreePane.this.createDsName(this.getNamePrefix()), false);} 然后调用TableDat…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...

阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)
cd /home 进入home盘 安装虚拟环境: 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境: virtualenv myenv 3、激活虚拟环境(激活环境可以在当前环境下安装包) source myenv/bin/activate 此时,终端…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...