【C++庖丁解牛】自平衡二叉搜索树--AVL树

🍁你好,我是 RO-BERRY
📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识
🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油

在这里插入图片描述


目录

  • 前言
  • 1 AVL树的概念
  • 2. AVL树节点的定义
  • 3. AVL树的插入
  • 4. AVL树的旋转
    • 实现代码
  • 5 AVL树的验证
  • 6 AVL树的删除(了解)
  • 7 AVL树的性能


前言

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

1 AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  1. 它的左右子树都是AVL树
  2. 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  3. 平衡因子 = 右子树高度 - 左子树高度

平衡因子并不是必须的,它只是一种控制方式,帮助我们更便捷的控制树

【扩充】
这里为什么高度差为1?如果高度相等不是更平衡吗?

节点是一个一个插入的,有些情况是无法做到相等的,最优就是高度差是1;比如:两个节点的树和四个节点的树等等。

在这里插入图片描述

2. AVL树节点的定义

AVL树节点的定义:

template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;   // 该节点的左孩子AVLTreeNode<T>* _pRight;  // 该节点的右孩子AVLTreeNode<T>* _pParent; // 该节点的双亲T _data;int _bf;                  // 该节点的平衡因子
};

3. AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

插入节点会影响新增节点的部分祖先
更新原则:

  • 若是插入的是左节点则父节点的平衡因子减1
  • 若是插入的是右节点则父节点的平衡因子加1

是否要继续更新取决于新增节点会不会影响父节点的高度,从而决定会不会影响爷爷节点

  • 更新后,父节点所在的子树高度不变则不会影响爷爷节点

说明父节点的平衡因子是1或者-1,父节点在矮的那边插入了节点,左右均衡了,于是父节点的高度不变,则不会影响到爷爷,更新结束

  • 更新后,父节点所在的子树高度改变则会影响爷爷节点

说明更新前,父节点的平衡因子是0,父节点变得不均衡,但是不违反规则,高度改变,会影响爷爷节点继续往上更新

更新后,父节点的平衡因子为2或-2,说明该子树违反了平衡规则,需要处理->旋转
代码实现:

	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;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性/*pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可此时:pParent的平衡因子可能有三种情况:0,正负1, 正负21. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理*/while (parent){// 更新双亲的平衡因子if (cur == parent->left){parent->_bf--;}else{parent->_bf++;}// 更新后检测双亲的平衡因子if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){// 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉树的高度增加了一层,因此需要继续向上调整cur = cur->_parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以pParent为根的树进行旋转处理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{RotateRL(parent);}break;}else{// 插入之前AVL树就有问题assert(false);}}return true;}

4. AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,
使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

  1. 新节点插入较高左子树的左侧—左左:右单旋

在这里插入图片描述
上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。

在旋转过程中,有以下几种情况需要考虑:

  1. 30节点的右孩子可能存在,也可能不存在
  2. 60可能是根节点,也可能是子树
  • 如果是根节点,旋转完成后,要更新根节点
  • 如果是子树,可能是某个节点的左子树,也可能是右子树
void _RotateR(PNode pParent)
{// pSubL: pParent的左孩子// pSubLR: pParent左孩子的右孩子PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋转完成之后,30的右孩子作为双亲的左孩子pParent->_pLeft = pSubLR;// 如果30的左孩子的右孩子存在,更新亲双亲if (pSubLR)pSubLR->_pParent = pParent;// 60 作为 30的右孩子pSubL->_pRight = pParent;// 因为60可能是棵子树,因此在更新其双亲前必须先保存60的双亲PNode pPParent = pParent->_pParent;// 更新60的双亲pParent->_pParent = pSubL;// 更新30的双亲pSubL->_pParent = pPParent;// 如果60是根节点,根新指向根节点的指针if (NULL == pPParent){_pRoot = pSubL;pSubL->_pParent = NULL;}else{// 如果60是子树,可能是其双亲的左子树,也可能是右子树if (pPParent->_pLeft == pParent)pPParent->_pLeft = pSubL;elsepPParent->_pRight = pSubL;}// 根据调整后的结构更新部分节点的平衡因子pParent->_bf = pSubL->_bf = 0;
}
  1. 新节点插入较高右子树的右侧—右右:左单旋
    在这里插入图片描述
    实现及情况考虑可参考右单旋。

  2. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋

在这里插入图片描述
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。

// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整
void _RotateLR(PNode pParent)
{PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子int bf = pSubLR->_bf;// 先对30进行左单旋_RotateL(pParent->_pLeft);// 再对90进行右单旋_RotateR(pParent);if(1 == bf)pSubL->_bf = -1;else if(-1 == bf)pParent->_bf = 1;
}
  1. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋

在这里插入图片描述
参考右左双旋。
总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
  • 当pSubR的平衡因子为1时,执行左单旋
  • 当pSubR的平衡因子为-1时,执行右左双旋
  1. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
  • 当pSubL的平衡因子为-1是,执行右单旋
  • 当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

实现代码

#pragma oncetemplate<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorpair<K, V> _kv;AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public: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;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent){if (cur == parent->left){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = 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{RotateRL(parent);}break;}else{// 插入之前AVL树就有问题assert(false);}}return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppnode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf = 0;subR->_bf = 0;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppnode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}subL->_bf = 0;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->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}private:Node* _root = nullptr;
};

5 AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树

如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

  1. 验证其为平衡树
  • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  • 节点的平衡因子是否计算正确
int _Height(PNode pRoot);
bool _IsBalanceTree(PNode pRoot)
{// 空树也是AVL树if (nullptr == pRoot) return true;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(pRoot->_pLeft);int rightHeight = _Height(pRoot->_pRight);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (diff != pRoot->_bf || (diff > 1 || diff < -1))return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);}

6 AVL树的删除(了解)

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。具体实现学生们可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

AVL树是一种自平衡的二叉搜索树,它的删除操作与插入操作类似,但需要在删除节点后进行平衡操作以保持树的平衡性。
AVL树的删除操作可以分为以下几种情况:

  1. 如果待删除的节点是叶子节点,直接删除即可。
  2. 如果待删除的节点只有一个子节点,将子节点替代待删除节点的位置。

如果待删除的节点有两个子节点,可以选择以下两种方式进行删除:

  • 找到待删除节点的前驱或后继节点,将其值复制到待删除节点,并将前驱或后继节点删除。
  • 找到待删除节点的左子树中的最大值或右子树中的最小值,将其值复制到待删除节点,并将最大值或最小值节点删除。

删除节点后,需要从被删除节点的父节点开始向上回溯,检查每个祖先节点是否平衡。如果发现某个祖先节点不平衡,需要进行相应的旋转操作来恢复平衡。

7 AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

AVL树是一种自平衡二叉搜索树,它的性能相对于普通的二叉搜索树更加稳定。AVL树的性能可以从以下几个方面来介绍:

  1. 查找操作:AVL树的查找操作与普通的二叉搜索树相同,时间复杂度为O(log n),其中n为树中节点的数量。由于AVL树是平衡的,所以查找操作的性能是稳定的。

  2. 插入和删除操作:AVL树在插入和删除节点时会进行自平衡操作,以保持树的平衡性。这些自平衡操作包括旋转和重新计算节点的平衡因子。插入和删除操作的时间复杂度也是O(log n),但由于需要进行自平衡操作,所以相对于普通二叉搜索树,AVL树的插入和删除操作可能会更耗时。

  3. 平衡性:AVL树的平衡性保证了树的高度始终保持在一个较小的范围内。具体来说,AVL树的任意节点的左右子树高度差不超过1。这种平衡性保证了查找、插入和删除操作的时间复杂度都能够保持在O(log n)。

  4. 空间复杂度:AVL树的空间复杂度与节点数量成正比,即O(n)。每个节点需要存储键值对以及额外的平衡因子信息。

总的来说,AVL树在查找操作上具有较好的性能,但在插入和删除操作上可能会稍微慢一些。然而,AVL树的平衡性保证了树的高度始终保持在一个较小的范围内,从而保证了整体的性能稳定性。

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

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

相关文章

Solidity Uniswap V2 Router swapTokensForExactTokens

最初的router合约实现了许多不同的交换方式。我们不会实现所有的方式&#xff0c;但我想向大家展示如何实现倒置交换&#xff1a;用未知量的输入Token交换精确量的输出代币。这是一个有趣的用例&#xff0c;可能并不常用&#xff0c;但仍有可能实现。 GitHub - XuHugo/solidit…

基础布局之RelativeLayout相对布局

目录 概述一、属性分类二、父容器定位属性2.1 示例12.2 示例2 三、相对定位属性3.1 示例13.2 示例23.3 示例3 概述 相对布局&#xff08;RelativeLayout&#xff09;是一种根据父容器和兄弟控件作为参照来确定控件位置的布局方式。 使用相对布局&#xff0c;需要将布局节点改成…

Spring Boot 使用 Redis

1&#xff0c;Spring 是如何集成Redis的&#xff1f; 首先我们要使用jar包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><gro…

基于ssm校园教务系统论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对校园教务信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差…

Windows 11 专业版 23H2 Docker Desktop 下载 安装 配置 使用

博文目录 文章目录 Docker Desktop准备系统要求 (WSL 2 backend)在 Windows 上打开 WSL 2 功能先决条件开启 WSL 2 WSL下载安装启动配置使用镜像 Image卷积 Volumes容器 Containers 命令RedisMySQLPostGreSQL Docker Desktop Overview of Docker Desktop Docker Desktop 疑难解…

每日面经分享(pytest入门)

1. pytest具有什么功能 a. 自动发现和执行测试用例&#xff1a;pytest可以自动发现项目中的测试文件和测试函数&#xff0c;无需手动编写测试套件或测试运行器。 b. 丰富的断言函数&#xff1a;pytest提供了丰富的断言函数&#xff0c;方便地验证测试结果是否符合预期。断言函…

隐私计算实训营学习五:隐语PSI介绍及开发指南

文章目录 一、SPU 实现的PSI介绍1.1 PSI定义和种类1.1.1 PSI定义和种类1.1.2 隐语PSI功能分层 1.2 SPU 实现的PSI介绍1.2.1 半诚实模型1.2.2 PSI实现位置 二、SPU PSI调度架构三、Secretflow PSI开发指南四、隐语PSI后续计划 一、SPU 实现的PSI介绍 1.1 PSI定义和种类 1.1.1 …

C++心决之命名空间、重载函数和引用

目录 1. C关键字(C98) 2. 命名空间 2.1 命名空间定义 2.2 命名空间使用 3. C输入&输出 4. 缺省参数 4.1 缺省参数概念 4.2 缺省参数分类 5. 函数重载 5.1 函数重载概念 5.2 C支持函数重载的原理--名字修饰(name Mangling) 6. 引用 6.1 引用概念 6.2 引用特性…

Apollo配置中心使用

apollo配置中心使用 Apollo配置中心Apollo配置中心-简介apollo源码Apollo配置基本概念Apollo特性Apollo基础模型Apollo架构设计Apollo架构设计-实时推送设计Apollo架构设计-可用性Apollo架构设计-监控Apollo架构设计-扩展Apollo-本地部署准备工作安装步骤mysql命令行创建Apollo…

MultiPath HTTP:北大与华为合作部署FLEETY

当前的终端基本都能支持蜂窝网络和wifi网络&#xff0c;然而&#xff0c;不同的网络通路都不可避免的会出现信号不好或者其他因素引起的通路性能(吞吐量、时延等)下降。为了能够提升终端业务体验&#xff0c;很多不同的MultiPath方案被提出&#xff0c;其中&#xff0c;包括应用…

【数据分析面试】1. 计算年度收入百分比(SQL)

题目 你需要为公司的营收来源生成一份年度报告。计算截止目前为止&#xff0c;在表格中记录的第一年和最后一年所创造的总收入百分比。将百分比四舍五入到两位小数。 示例&#xff1a; 输入&#xff1a; annual_payments 表 列名类型amountINTEGERcreated_atDATETIMEstatusV…

CVAE——生成0-9数字图像(Pytorch+mnist)

1、简介 CVAE&#xff08;Conditional Variational Autoencoder&#xff0c;条件变分自编码器&#xff09;是一种变分自编码器&#xff08;VAE&#xff09;的变体&#xff0c;用于生成有条件的数据。在传统的变分自编码器中&#xff0c;生成的数据是完全由潜在变量决定的&…

STM32 字符数组结束符 “\0”

STM32 字符数组结束符 “\0” 使用字符数组使用printf&#xff0c;string参考 使用字符数组 使用STM32的串口发送数据&#xff0c;核心代码如下&#xff1a; char str[] "hello world!\n\r";while(1) {HAL_UART_Transmit(&huart2, str, sizeof (str), 10);HAL…

设计模式深度解析:AI如何影响装饰器模式与组合模式的选择与应用

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 AI如何影响装饰器模式与组合模式的选择与应用 在今天这个快速发展的技术时代&#…

高阶SQL语句(二)

一 子查询 也被称作内查询或者嵌套查询&#xff0c;是指在一个查询语句里面还嵌套着另一个查询语 句。子查询语句 是先于主查询语句被执行的&#xff0c;其结果作为外层的条件返回给主查询进行下一 步的查询过滤。 ①子语句可以与主语句所查询的表相同&#xff0c;也可以是不…

解决WSL更新速度慢的方案

在Windows上安装Docker Desktop时&#xff0c;如果选择使用WSL&#xff0c;则可能会出现在运行程序前要求升级WSL的步骤。程序会提示使用下面指令来升级 wsl.exe --update但是升级速度特别慢&#xff0c;于是在网络不稳定的情况下经常会出现下载失败的情况。 百度里一直没搜到…

2024中国(杭州)国际数字物流技术与应用展览会将于7月8日举办

2024中国&#xff08;杭州&#xff09;国际数字物流技术与应用展览会 2024年7月8-10日 | 杭州国际博览中心 同期举办&#xff1a;2024长三角快递物流供应链与技术装备展览会 数字贸易创新引领合作动能 《十四五规划》明确指出关于“加快数字化发展&#xff0c;建设数字中国…

STM32CubeMX学习笔记28---FreeRTOS软件定时器

一、软件定时器简介 1 、基本概念 定时器&#xff0c;是指从指定的时刻开始&#xff0c;经过一个指定时间&#xff0c;然后触发一个超时事件&#xff0c;用户 可以自定义定时器的周期与频率。类似生活中的闹钟&#xff0c;我们可以设置闹钟每天什么时候响&#xff0c; 还能设置…

微服务demo(三)nacosfeign

一、feign使用 1、集成方法 1.1、pom consumer添加依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>2.2.6.RELEASE</version></dependency&…

C++刷题篇——08字符串重新排列

一、题目 二、解题思路 1、先对每个单词内部进行排序&#xff0c;再对单词间进行排序 2、使用map&#xff0c;key为单词&#xff0c;value为出现的次数 3、由于要对map排序&#xff0c;构造pair型的一维数组&#xff0c;将map的key、value放进去 4、构造函数&#xff0c;按照次…