AVL树性质和实现

AVL树

AVL是两名俄罗斯数学家的名字,以此纪念

与二叉搜索树的区别

AVL树在二叉搜索树的基础上增加了新的限制:需要时刻保证每个树中每个结点的左右子树高度之差的绝对值不超过1

因此,当向树中插入新结点后,即可降低树的高度,从而减少平均搜索长度

平衡因子

为了能方便处理平衡二叉搜索树的限制条件,通常会引入平衡因子的概念

某一节点的平衡因子=其右子树高度-其左子树高度

在AVL树中,并不是一定需要平衡因子的,有些代码的AVL树就没有平衡因子。

这里引入平衡因子只是更方便的去判断树是否平衡了

AVL树的效率推算

image-20231013200846180

我们知道树的增删查改的效率是与树的高度有关的

假如AVL树是满二叉树,此时:2h-1=N

假如AVL树不是满二叉树,设最底层的节点个数为X,此时:2h-X=N。X的范围为[1,最后一层结点树-1]

此时上述两种情况都可以得出树的高度的数量级为logN,因此增删查改的时间复杂度为O(logN)

AVL树的节点设计

首先,与二叉搜索树相比,每个节点多了一个平衡因子(balance factor)

其次,为了满足一定需求,还多了个_parent指针,指向节点的父亲

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){}
};

这是三叉链image-20231018232522035

AVL树的框架设计

template<class K,class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public://……
private:Node* _root=nullptr;
};

AVL树的插入

在面试时几乎不会让你手撕AVL树的插入。重要的是了解思想。

但让你手撕一个旋转是有可能的

插入的逻辑

AVL树是在二叉搜索树的基础上引入了平衡因子,因此AVL树的插入分为两个步骤:

  • 按照二叉搜索树的方式插入新节点(多了个链接_parent
  • 插入后,更新节点的平衡因子

首先注意:新插入的节点只会对其父亲和其祖先的平衡因子造成影响

插入节点的_parent的平衡因子是一定需要调整的;在插入之前其_parent的平衡因子有三个可能值1、-1、0,插入节点后:

  • 如果插入到_parent的左侧,则_parent的平衡因子–即可
  • 如果插入到_parent的右侧,则_parent的平衡因子++即可

此时:_parent的平衡因子可能有三大种情况:

  • 如果_parent的平衡因子为0,说明插入之前的平衡因子为正负1,插入后被调整成0,满足AVL树的性质,插入成功,插入结束
  • 如果_parent的平衡因子为±1,说明插入之前的平衡因子为0,此时树的高度增加,那么就需要继续向上更新祖先的平衡因子,直至某一祖先的平衡因子为0或者更新到根节点,才算插入成功,停止更新,插入结束。
  • 如果_parent的平衡因子为±2,此时违反了AVL树的性质,需要进行旋转处理。处理完成则算插入成功,插入结束
更新平衡因子

最坏的情况是一直更新到根,如下图:image-20231017154509542

因此在更新平衡因子时,我们的循环条件为:while(parent)

因为只有根节点的_parent为空。所以当更新完根节点的平衡因子后,循环结束

while (parent)
{if (cur == parent->_left)//节点插入在父亲左边{parent->_bf--;}else if (cur == parent->_right)//节点插入在父亲右边{parent->_bf++;}//进一步判断祖先节点的平衡因子if (parent->_bf == 0)//父亲的平衡因子为0,循环结束{break;}else if (parent->_bf == 1 || parent->_bf == -1)//父亲的平衡因子为1或-1,则需要继续向上调整{cur = parent;parent = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//父亲的平衡因子为2或-2,则需要旋转,且选择完后树一定平衡,故结束循环{//左单旋if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}//右单旋if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//右左双旋if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}//左右双旋if (parent->_bf == -2 &&	 cur->_bf == 1){RotateLR(parent);}break;}else//其他情况,此时说明在插入之前树就已经不是平衡树了{assert(false);}
}

最后一个else中的assert(false)看似是无用的,因为parent不可能是绝对值大于2的。但是代码都是人写的,不可排除一开始的树就是有问题的。因此这句代码很重要

AVL树的旋转

根据节点插入位置的不同,AVL树的旋转分为四种:

  • 新节点插入较高左子树的左侧–左左:右单旋
  • 新节点插入较高右子树的右侧–右右:左单旋
  • 新节点插入较高左子树的右侧—左右:先左单旋再右单旋(左右双旋)
  • 新节点插入较高右子树的左侧—右左:先右单旋再左单旋(右左双旋)

何时使用何种旋转:

假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑:

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR

    • 当pSubR的平衡因子为1时,执行左单旋

    • 当pSubR的平衡因子为-1时,执行右左双旋

  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL

    • 当pSubL的平衡因子为-1是,执行右单旋

    • 当pSubL的平衡因子为1时,执行左右双旋

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

右单旋

image-20231019154016105

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

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

  1. cur节点的右孩子可能存在,也可能不存在

  2. parent可能是根节点,也可能是子树

    如果是根节点,旋转完成后,要更新根节点

    如果是子树,可能是某个节点的左子树,也可能是右子树

这两点是所有旋转情况都需要考虑的

右单旋的核心操作:把cur的右孩子给到parent的左,再把parent给到cur的右

代码:

void RotateR(Node* parent)//parent的平衡因子绝对值为2
{Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;//把cur的右孩子给到parent的左if (curright)//如果cur的右孩子存在,则更新其父亲为parent{curright->_parent = parent;}cur->_right = parent;//再把parent给到cur的右Node* ppnode = parent->_parent;//记录parent的原父亲节点,用于对cur的父亲进行更新parent->_parent = cur;//更新parent的父亲//对cur的父亲进行更新if (parent == _root)//parent即为根节点{_root = cur;cur->_parent = nullptr;//那么cur作为新的根,其父亲为空}else//parent是子树{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->_bf = parent->_bf = 0;//更新完后,平衡因子一定为0
}
左单旋

image-20231019153929844

左单旋的核心操作与右单旋的核心操作正好是镜像的

左单旋的核心操作:把cur的左给parent的右,再把parent给到cur的左

代码:

void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;//把cur的左给parent的右if (curleft)//如果cur的左存在,则更新其父亲{curleft->_parent = parent;} cur->_left = parent;//再把parent给到cur的左Node* ppnode = parent->_parent;//记录parent的原父亲节点,用于对cur的父亲进行更新parent->_parent = cur;//对cur的父亲进行更新if (parent == _root){_root = cur;cur->_parent = nullptr;}else//说明左旋的部分只是某棵树的局部{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->_bf = parent->_bf = 0;
}
右左双旋

image-20231019155022899

先对90进行右单旋,再对30进行左单旋

两次旋转。第一次旋转使其变成单纯的右边高,第二次旋转对应的左单旋

双旋代码难的不是旋转,而是平衡因子的更新

从图中可以看到:最终60成了根,60的左孩子给了parent的右边,60的右孩子给了cur的左边

因此平衡因子的更新分为两种情况:

  • h == 0:那么60则作为新插入的节点,此时60的bf == 0,那么parent和cur的bf也一定为0
  • h>=0:
    • 假如新结点插入在60的左边,即60的bf == -1。那么最终parent的bf == 0,cur的bf == 1
    • 假如新结点插入在60的右边,即60的bf == 1。那么最终parent的bf == -1,cur的bf == 0
    • 而60作为根节点最终bf一定为0

代码:

void RotateRL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;//先右旋再左旋RotateR(cur);RotateL(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}else{assert(false);}
}
左右双旋

image-20231019160543333

与右左双旋是镜像的,不再赘述

代码:

void RotateLR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;//先左旋再右旋RotateL(cur);RotateR(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else{assert(false);}
}
AVL树插入的完整代码
bool Insert(const pair<K, V>& kv)
{//先帮助插入节点找到正确位置if (_root == nullptr)//树为空{_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){//插入节点大于根节点if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first)//插入节点小于根节点{parent = cur;cur = cur->_left;}else//所插入节点已经存在{return false;}}cur = new Node(kv);if (cur->_kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//控制平衡因子while (parent)//{if (cur == parent->_left)//节点插入在父亲左边{parent->_bf--;}else if (cur == parent->_right)//节点插入在父亲右边{parent->_bf++;}//进一步判断祖先节点的平衡因子if (parent->_bf == 0)//父亲的平衡因子为0,循环结束{break;}else if (parent->_bf == 1 || parent->_bf == -1)//父亲的平衡因子为1或-1,则需要继续向上调整{cur = parent;parent = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//父亲的平衡因子为2或-2,则需要旋转,且选择完后树一定平衡,故结束循环{//左单旋if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}//右单旋if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//右左双旋if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}//左右双旋if (parent->_bf == -2 &&	 cur->_bf == 1){RotateLR(parent);}break;}else//其他情况,此时说明在插入之前树就已经不是平衡树了{assert(false);}}}//左单旋
void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;} cur->_left = parent;Node* ppnode = parent->_parent;//记录parent的原父亲节点parent->_parent = cur;//对cur的父亲进行更新if (parent == _root){_root = cur;cur->_parent = nullptr;}else//说明左旋的部分只是某棵树的局部{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->_bf = parent->_bf = 0;
}//右单旋
void RotateR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;//让右节点接到parent的左边,再将parent接到cur的右边parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;Node* ppnode = parent->_parent;//记录parent的原父亲节点parent->_parent = cur;//对cur的父亲进行更新if (parent == _root){_root = cur;cur->_parent = nullptr;}else//说明右旋的部分只是某棵树的局部{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->_bf = parent->_bf = 0;
}//右左双旋
void RotateRL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;//先右旋再左旋RotateR(cur);RotateL(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}else{assert(false);}
}//左右双旋
void RotateLR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;//先左旋再右旋RotateL(cur);RotateR(parent);//更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 1;cur->_bf = -1;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else{assert(false);}
}

AVL树的验证

验证AVL树分为两步:

  • 验证其为二叉搜索树:如果中序遍历历可得到一个有序的序列,就说明为二叉搜索树(这里就不详细介绍了,详情可以看二叉搜索树那里)

  • 验证其为平衡树:

    • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    • 节点的平衡因子是否计算正确
    int TreeHeight(Node* root)
    {if (root == nullptr)return 0;int leftHeight = TreeHeight(root->_left);int rightHeight = TreeHeight(root->_right);return rightHeight > leftHeight ? rightHeight + 1: leftHeight + 1;
    }bool IsBalance()//两个IsBalance构成重载
    {return IsBalance(_root);
    }bool IsBalance(Node* root)
    {if (root == nullptr)return true;int leftHeight = TreeHeight(root->_left);int rightHeight = TreeHeight(root->_right);if (rightHeight - leftHeight != root->_bf){cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;return false;}return abs(rightHeight - leftHeight) < 2&& IsBalance(root->_left)&& IsBalance(root->_right);
    }
    

    这里给出一个调试技巧:

    image-20231018182647226假如我运行出现了下面的情况

    image-20231018182657070

    我们现在知道在插入11的时候出了问题,那么就可以针对e=11时进行调试

    但如果现在有100个值,我在第99个值才出现问题,那是不是需要按F10按99次呢?

    两种方法,:

    • 一个是利用条件断点

      image-20231018183053645

    • 还一个是我们自己写代码让它停到想停的地方

      image-20231018183141199

      这里的int x=0;是随便写的,目的是能让断点在这里停下来。因为断点打在空行上是停不住的

      当然,我停在断点处不是为了调int x=0;,而是为了调下面

AVL树的删除

了解即可

image-20231019171629540

总结

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)

但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

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

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

相关文章

nn.embedding函数详解(pytorch)

提示&#xff1a;文章附有源码&#xff01;&#xff01;&#xff01; 文章目录 前言一、nn.embedding函数解释二、nn.embedding函数使用方法四、模型训练与预测的权重变化探讨 前言 最近发现prompt工程(如sam模型)&#xff0c;也有transform的detr模型等都使用了nn.Embedding函…

数据结构大体体系

逻辑结构 线性结构线性表一串珠子用线连起来&#xff0c;这就是典型的“线性存储结构”。每颗珠子之间的关系结构也很简单&#xff0c;包括头尾的话&#xff0c;它们最少有一个关系对象&#xff0c;而中间的珠子无论前后都只有一个关系对象&#xff0c;即 one-to-one栈队列字符…

Chatgpt人工智能对话源码系统分享 带完整搭建教程

ChatGPT的开发基于大规模预训练模型技术。预训练模型是一种在大量文本数据上进行训练的模型&#xff0c;可以学习到各种语言模式和知识。在ChatGPT中&#xff0c;预训练模型被用于学习如何生成文本&#xff0c;并且可以用于各种不同的任务&#xff0c;如对话生成、问答、摘要等…

时序预测 | MATLAB实现基于LSSVM-Adaboost最小二乘支持向量机结合AdaBoost时间序列预测

时序预测 | MATLAB实现基于LSSVM-Adaboost最小二乘支持向量机结合AdaBoost时间序列预测 目录 时序预测 | MATLAB实现基于LSSVM-Adaboost最小二乘支持向量机结合AdaBoost时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于LSSVM-Adaboos…

全球10米土地覆盖产品(ESA)数据集2020和2021年

简介 全球10米土地覆盖产品(ESA)来源于欧空局&#xff0c;是基于哨兵一号、哨兵二号数据制作的2020年的10m分辨率的全球土地覆盖数据。土地利用数据一共分为11类&#xff0c;分别是:林地、灌木、草地、耕地、建筑、裸地/稀疏植被区、雪和冰、开阔水域、草本湿地、红树林、苔藓…

贰[2],QT异常处理

1&#xff0c;异常&#xff1a;QT编译警告 warning LNK4042: 对象被多次指定&#xff1b;已忽略多余的指定 处理办法&#xff0c;检查.pri文件&#xff0c;是否关联了多个相同的文件(头文件.h/源文件.cpp) 2&#xff0c;异常&#xff1a;C4819: 该文件包含不能在当前代码页(936…

云尘 命令执行系列

第一题 system <?php include "flag.php";if (isset($_POST[cmd])) {system($_POST[cmd]); }show_source(__FILE__);代码如上 system($_POST[cmd]); POST请求发送一个名为 cmd 的参数&#xff0c;然后将该参数的值传递给系统命令执行函数 system()&#xff0c…

C语言学习笔记之结构篇

C语言是一门结构化程序设计语言。在C语言看来&#xff0c;现实生活中的任何事情都可看作是三大结构或者三大结构的组合的抽象&#xff0c;即顺序&#xff0c;分支&#xff08;选择&#xff09;&#xff0c;循环。 所谓顺序就是一条路走到黑&#xff1b;生活中在很多事情上我们都…

Spring Boot项目中通过 Jasypt 对属性文件中的账号密码进行加密

下面是在Spring Boot项目中对属性文件中的账号密码进行加密的完整步骤&#xff0c;以MySQL的用户名为root&#xff0c;密码为123321为例&#xff1a; 步骤1&#xff1a;引入Jasypt依赖 在项目的pom.xml文件中&#xff0c;添加Jasypt依赖&#xff1a; <dependency><…

ClickHouse 学习之从高级到监控以及备份(二)

第 一 部分 高级篇 第 1 章 Explain 查看执行计划 在 clickhouse 20.6 版本之前要查看 SQL 语句的执行计划需要设置日志级别为 trace 才能可以看到&#xff0c;并且只能真正执行 sql&#xff0c;在执行日志里面查看。在 20.6 版本引入了原生的执行计划的语法。在 20.6.3 版本成…

ubuntu 20.04 server安装

ubuntu 20.04 server安装 ubuntu-20.04.6-live-server-amd64.iso 安装 安装ubuntu20.04 TLS系统后&#xff0c;开机卡在“A start job is running for wait for network to be Configured”等待连接两分多钟。 cd /etc/systemd/system/network-online.target.wants/在[Servi…

揭开堆叠式自动编码器的强大功能

一、介绍 在不断发展的人工智能和机器学习领域&#xff0c;深度学习技术因其处理复杂和高维数据的能力而广受欢迎。在各种深度学习模型中&#xff0c;堆叠式自动编码器是一种多功能且功能强大的工具&#xff0c;可用于特征学习、降维和数据表示。本文探讨了堆叠式自动编码器在深…

R语言实操记录——导出高清图片(矢量图)

R语言 R语言实操记录——导出高清图片&#xff08;矢量图&#xff09; 文章目录 R语言一、起因&#xff08;闲聊&#xff0c;可跳过&#xff09;二、如何在R中导出高清图片&#xff08;矢量图&#xff09;2.1、保存为EPS图片格式后转AI编辑2.2、保存为PDF格式&#xff08;推荐…

LabVIEW实现变风量VAV终端干预PID控制

LabVIEW实现变风量VAV终端干预PID控制 变风量&#xff08;VAV&#xff09;控制方法的研究一直是VAV空调研究的重点。单端PID控制在温差较大时&#xff0c;系统容易出现过冲。针对空调终端单端PID控制的不足&#xff0c;设计一种干预控制与PID控制耦合的控制方法。项目使用LabV…

关于Alibaba Cloud Toolkit 下载配置以及后端自动部署

idea中File-Settings-Plugins 搜索Alibaba Cloud Toolkit点击下载&#xff0c;下载完成重启 1、点击 Tools-Alibaba Cloud-Deploy to Host 部署到主机 2、配置服务器ip、jar包启动命令、服务器jar存放位置 3、设置服务器ip用户名密码&#xff0c;点击测试连接情况 4、配置脚本…

Flink SQL TopN语句详解

TopN 定义&#xff08;⽀持 Batch\Streaming&#xff09;&#xff1a; TopN 对应离线数仓的 row_number()&#xff0c;使⽤ row_number() 对某⼀个分组的数据进⾏排序。 应⽤场景&#xff1a; 根据 某个排序 条件&#xff0c;计算 某个分组 下的排⾏榜数据。 SQL 语法标准&am…

基于Java+SpringBoot+LayUI仓库管理系统

一.项目介绍 本项目是使用JavaSpringBoot开发&#xff0c;可以实现仓库的注册、登录&#xff0c;登录后可进入系统&#xff0c;进行客户管理、供应商管理、商品管理、商品退货查询管理、登录日志及退出等几大模块。系统界面采用传统的后台管理界面&#xff0c;界面简单、直观。…

【大数据】NiFi 中的处理器(一):GenerateTableFetch

NiFi 中的处理器&#xff08;一&#xff09;&#xff1a;GenerateTableFetch 1.简介2.应用场景3.示例3.1 案例一&#xff1a;无输入流文件&#xff0c;来源表含增量字段3.2 案例二&#xff1a;无输入流文件&#xff0c;不含增量字段3.3 案例三&#xff1a;无输入流文件&#xf…

Transformer的最简洁pytorch实现

目录 前言 1. 数据预处理 2. 模型参数 3. Positional Encoding 4. Pad Mask 5. Subsequence Mask 6. ScaledDotProductAttention 7. MultiHeadAttention 8. FeedForward Networks 9. Encoder Layer 10. Encoder 11. Decoder Layer 12. Decoder 13. Transformer 1…

【单片机基础小知识-如何通过指针来读写寄存器】

寄存器的本质就是内存&#xff0c;RAM&#xff0c;而指针是可以对内存进行操作的&#xff0c;因此可以通过指针来读写寄存器。 如何读取以下一片地址&#xff1a; 步骤1、首地址 结构体&#xff0c;它所占用的内存空间大小与它内部成员有关。 构造一个28字节的类型 type…