当前位置: 首页 > news >正文

手撕AVL树

引入:为何要有AVL树,二次搜索树有什么不足?

二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此产生了AVL树,该结构对二叉树进行了平衡处理,AVL树的时间复杂度永远为O(logn)
下图为二叉搜索树退化为单支树的场景:

不会搜索二叉树,是看不懂此篇博客的。先去看: 搜索二叉树的模拟实现-CSDN博客

一:AVL树的概念

如果一棵二叉搜索树是高度平衡的(树中任意一颗子树的左右子树高度差不超过1),它就是AVL树。
且一颗AVL树具有以下性质:它的左右子树都是AVL
这不纯纯废话吗,树中任意一颗子树的左右子树高度差不超过1,那每颗子树不都是AVL树吗~
下图为AVL树:

其满足树中的任意一颗子树的左右子树高度差不超过1

Q:节点旁边的数字代表什么?

A:实现AVL树的方式多种多样,博主采取的是平衡因子法来实现,图中节点旁边的数字即代表该节点的平衡因子大小,平衡因子值 = 该节点的右子树高度-左子树高度,其能反映该树是否为AVL树,平衡因子>1 或 <-1则代表该树不为AVL树,则需要通过一些列的处理,让其再成为AVL树

Q:高度差不超过0,不是更平衡吗

A:某些个数的节点,永远无法达到0的高度差,如2个节点,4个节点,所以最优就是高度差为1

注意:

①:高度指的是该子树的最高高度,例如节点5的右子树高度是3 而不是7->6此路径的高度2

②:博主的_bf表示的是:某个节点的右子树高度-左子树高度的值,某些实现也有可能是左-右,这里以博主自己的右-左来讲解

二:AVL树实现

一个结构只要包含节点,实现的框架都是两个类:这里是节点类和AVL树类(list就是list类)

1:节点类的实现

//节点类
template<class K, class V>
struct AVLTreeNode
{//成员变量AVLTreeNode<K, V>* _left;//左指针AVLTreeNode<K, V>* _right;//右指针AVLTreeNode<K, V>* _parent;//父指针int _bf; //平衡因子(balance factor的缩写) 代表某个节点的右子树高度-左子树高度的值pair<K, V> _kv;//每个节点的kv值//构造函数AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr) , _bf(0) , _kv(kv) {}
};

解释:

①:AVL树是对KV模型的搜索二叉树的升级,所以AVL树照样是KV结构的

②:博主还设置了父指针,方便后序的操作

2:AVL类的实现

AVL树的实现很多,一步一步讲解吧~ 先看框架

a:框架
template<class K, class V>
class AVLTree
{//方便使用节点类 只需Node即可typedef AVLTreeNode<K, V> Node;
public://......各种函数private://成员变量_root根节点Node* _root = nullptr;
};
b: 插入函数(重难点)

注意:代码注释中博主有时候会简称parent节点为p节点

插入函数的思路:

找到插入的位置将其插入进去,插入后判断是否还是AVL树,不是则处理,让其恢复为AVL树

插入的情况:

a:插入后还是一颗AVL树

咋找到插入的地方进行插入即可

b:插入后不再是一颗AVL树

根据不再是AVL树的情况,进行对应的处理(旋转处理),让其恢复为AVL树

由于代码过长 分成以下4步来讲解和展示

①:如何找到插入的位置并插入

②:如何判断插入后是否还是AV树

③:不是AVL树的几种旋转处理

④:展示的总代码

①:如何找到插入的位置并插入
//插入函数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//AVL树不会存在相同的节点{return false;}}//走到这代表 找到了插入的位置 cur = new Node(kv);//先把该节点准备好//parent节点是找到的插入位置的父节点//所以现在将cur正确的链接到parent的正确方向if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//插入完成 则返回真return true;}

解释:

a:如何找到插入的位置 和 如何插入 和二叉搜索树是一样的,根据你插入的值的k值,来和根节点的k值比较,插入的k大,则往右子树找,反之左子树,如此循环,直到找到插入的位置,且找的途中遇到和插入的k值一样的节点,代表插入失败,因为AVL树不会存在两个相同的节 

b:成功找到插入的位置了,该位置一定原先为nullptr,此时再根绝原先为nullptr的节点和其父节点(代码注释中博主有时候会简称parent节点为p节点)的关系,来对parent节点和插入节点的链接

以上代码只是让插入的节点成功的插入到了该插入的位置,若是二叉搜索树到此也就结束了,但是AVL树插入之后,需要判断其是否还是一颗AVL树,是则不再处理,不是则需要处理

②:如何判断插入后是否还是AV树

Q:如何判断插入节点后,该树是否还是一颗AVL树呢?

A:根据插入节点后,该插入节点对于其祖先节点的_bf的值带来的改变来判断

插入对祖先节点的_bf的影响有以下几种:

解释:插入后p节点的_bf变为0,则代表插入前p节点的_bf为-1(只有一个左孩子) 或者 1(图示),此情况则代表插入节点只会影响p节点的_bf,因为对于p的parent节点来说,p这颗子树高度没变,所以次树还是一颗AVL树,无需处理

解释:插入后p节点的_bf变为1(图示)或者-1(插入到了节点9的左侧),则代表插入前p节点的_bf为0,此情况则代表插入一个节点后,不仅会影响p还会影响p的parent节点的_bf,此时的p的parent节点的_bf为2或者-2,皆代表不再是一颗AVL树,需要处理了

总结:

AVL树插入操作中平衡因子的更新规则

在AVL树中插入一个新节点时,需要更新相关祖先节点的平衡因子(Balance Factor, BF),并检查更新之后的平衡因子是否违反平衡条件。逻辑如下:


1. 受影响的节点

插入新节点后,需要从新节点的父节点开始,逐层向上更新,直到根节点或某个祖先节点的子树高度不再变化。


2. 平衡因子更新原则

设当前正在更新的_bf是节点为p,现在插入一个子节点为 c

  • 如果 c 是 p 的左孩子p->bf--(左子树高度增加)。

  • 如果 c 是 p 的右孩子p->bf++(右子树高度增加)。


3. 是否继续向上更新?

更新 p 的平衡因子后,根据其更新后的值来决定是否继续回溯更新祖先节点:

  1. 情况 1:p->bf == 0

    • 说明:更新前 p->bf 为 1 或 -1,插入操作使较矮的一侧子树高度增加,p 的左右子树变得平衡。

    • 影响:以p为根节点的这颗树高度不变,因此不会影响更高层祖先节点的平衡因子。

    • 操作终止更新祖先节点的_bf

  2. 情况 2:p->bf == 1 或 p->bf == -1

    • 说明:更新前 p->bf 为 0,插入操作使某一侧子树高度增加。

    • 影响p 的子树高度变化,会影响其父节点的平衡因子。

    • 操作继续向上更新祖先节点。

  3. 情况 3:p->bf == 2 或 p->bf == -2

    • 说明p 的平衡因子违反AVL树规则,需要通过处理(旋转操作)重新平衡子树。


所以 如何判断插入后是否还是AVL树 的代码如下:

        //插入完成一定会影响parent节点的bf(只是看cur是p的哪一边 bf再++或--)//p的bf三种情况:bf 0; 1/-1;2/-2//0:不会影响parent的祖先的bf 直接break//1/-1:树高度增加 会影响祖先的bf 所以更新完p的bf 再次循环继续更新上面的bf//2/-2:则需要旋转while (parent){//根据插入节点是p的哪一边来更新p的bfif (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//对更新完的p的bf判断if (parent->_bf == 0){break;}//祖先节点依旧需要更新bf  再次循环else if (parent->_bf == 1 || parent->_bf == -1){//向上更新两个变量cur = cur->_parent;parent = parent->_parent;}//来到这代表需要旋转//进入旋转,咋代表cur和parent在之前的循环中已经找到了_bf为2/-1的节点 则对该节点处理else if (parent->_bf == 2 || parent->_bf == -2){//进行旋转处理//旋转处理后 则代表恢复AVL树 break出去break;}

以上只是完成了判断插入的后时候是AVL树,是则不处理,不是则处理,那么不是AVL树的旋转处理是什么?

③:不是AVL树的几种旋转处理

旋转不只是一种,分为四种旋转,两种单旋,两种双旋

也就是说不是AVL树的情况分为四种,每种对应一种旋转来处理~

a:左单旋
新节点插入较高右子树的右侧-->需要 左单旋
如图:
其中的a b c 是一颗子树的抽象图  一开始abc都是高度为h的子树
有同学就要问了,直接举例子不好吗?为什么要用抽象图?
因为例子多到数不过来,用单一的例子反而不能代表全部情况,用抽象图更好

对上图的左单旋再画图讲解:

Q:这样操作下来,还符合搜索二叉树吗?b为什么能放在30的右 30又为什么能放在60的左

A:符合。b这颗子树的值都是比60小(其在60的左)且比30大(因为30的右子树都比30大),所以b可以放在30的右,30能放在60的左(30新增的右子树b,也比60小)

将左旋转转换为代码:

    //步骤: //①:给到p的右指针指向原先p的右孩子的左子树//②:p的右子树的的左指针指向p节点void RotateL(Node* parent)//参数的p节点 就是bf为2的节点{Node* subR = parent->_right; //p的右Node* subRL = subR->_left;   //p的右左parent->_right = subRL;//①if (subRL) //避免p的右左为空subRL->_parent = parent;subR->_left = parent;Node* ppnode = parent->_parent; //先记录p的父节点 以防p节点不是根节点parent->_parent = subR; //②if (parent == _root)//查看p是否为根节点{//p是根节点 则subR成为新的根接节点//再置一下subR父指针为空_root = subR;subR->_parent = nullptr;}else//p不是根 则p的父亲也需要正确的指向subR(判断原先的p是pp的左右孩子的哪一个){if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf = 0;subR->_bf = 0;}

解释: 看起来仅仅是需要执行步骤中的①② ,但实则会有几个问题:

①:若subRL为空的处理

②:subRL 和 p  的 父指针 需要重新指向父节点

③:30节点也就是左旋转函数的参数parent节点,是根节点和不是根节点的处理

④:修改平衡因子,按照上上图可知,左旋转,会让parent和subR的_bf为0

b:右单旋
新节点插入较高左子树的左侧需要--> 右单旋

右单旋和左单旋大同小异,不再过多赘述了

//①:p的左指针指向p原先的左孩子的右孩子//②:原先的p的左孩子的右指针指向p节点//代码逻辑和左旋转同理 不再赘述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;}
c:左右双旋

AVL在插入节点失去平衡的前提是,是有一颗子树较高(比另一测高1)

所以我们之前:

a右单旋,就是左子树为较高子树,且在该子树的左侧插入

b左单旋,就是右子树为较高子树,且在该子树的右侧插入

那有没有可能 插入较高左子树的右侧 或 较高右子树的左侧?

当然有,所以前者需要左右双旋 后者需要右左双旋

左右双旋示意图:

解释:先以30为旋转节点,对其左单旋,然后再以90为旋转节点,对其右单旋

只看最后一步和第一步的话。也可以理解为:40作根节点,30 60 做40左右孩子,40的左给30的右,40的右给60的左,一定是符合搜索二叉树的

新的问题:插入较高左子树的右测不止一种情况!!

如图所示,共有三种情况:

解释:每种情况虽然都可以用左右双旋恢复成AVL树,但是恢复之后某些节点的_bf值不一样,

如下图所示:

Q:那我们现在知道了不同的情况恢复为AVL树后的节点的_bf不同,那根据什么去判断不同的情况呢?

A:先明白插入一个节点后,是先更新整棵树需要更新_bf的节点,所以我们可以按照插入之后的subRL的_bf,也就是上图中的40节点的_bf值来判断,由图也可知,三种不同的插入情况,也只有subRL的_bf不同了,分别为-1;1;0,-1则对应左右旋转后的subRL的bf为0,subL的bf为0,parent的bf为1,其他两种不在赘述

所以左右旋转的代码如下:

//步骤 ://①:对p的左节点进行左单旋//②:再对p节点进行右单旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//由于在b树插入 在c树插入 或60本身就是插入的节点入 这三种情况双旋之后的树的bf不同//规律:根据插入节点的bf判断即可if (bf == -1)//代表在b树插入{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1)//代表在c树插入{subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}//else if (bf == 0)//代表subLR就是插入节点{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}

d:右左双旋

和左右双旋的情况类型,也是分为三种情况,不再赘述

代码如下:

//步骤 ://①:对p的右节点进行右单旋//②:再对p节点进行左单旋//代码逻辑类似 不再赘述void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else{parent->_bf = 0;subR->_bf = 0;}}
 ④:插入函数的总代码
//插入函数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//AVL树不会存在相同的节点{return false;}}//走到这代表 找到了插入的位置 cur = new Node(kv);//先把该节点准备好//parent节点在之前是cur的父节点 也就是空节点的父亲//所以现在能将cur正确的链接到parent的正确方向if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//插入完成一定会影响parent节点的bf(只是看cur是p的哪一边 bf再++或--)//p的bf三种情况:bf 0 1/-1 2/-2//0:不会影响parent的祖先的bf 直接break//1/-1:树高度增加 会影响祖先的bf 所以更新完p的bf 再次循环继续更新上面的bf//2/-2:则需要旋转while (parent){//第一次更新p的bfif (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//对更新完的p的bf判断if (parent->_bf == 0){break;}//祖先节点依旧需要更新bf  再次循环else if (parent->_bf == 1 || parent->_bf == -1){cur = cur->_parent;parent = parent->_parent;}//需要旋转//进入旋转,咋代表cur和parent 之前已经循环找了 需要旋转的p节点//(该p节点的bf 2/-2)else if (parent->_bf == 2 || parent->_bf == -2){// 左旋转// 触发条件:p->bf为2 cur->_bf == 1if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}// 右旋转// 触发条件:parent->_bf == -2 && cur->_bf == -1else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}// 左右旋转// 触发条件:parent->_bf == -2 && cur->_bf == 1else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}//除此之外右左旋转else{RotateRL(parent);}break;}else{// 插入之前AVL树就有问题assert(false);//标记“不应执行到此处”}}//插入完成 则返回真return true;}//左旋转//新节点插入较高右子树的右侧---简称右右//代表从次树的_root节点看右子树高1 且插入的节点在右子树的右侧//步骤: //①:给到p的右指针指向原先p的右孩子的左子树//②:p的右子树的的左指针指向p节点void RotateL(Node* parent)//参数的p节点 就是bf为2的节点{Node* subR = parent->_right; //p的右Node* subRL = subR->_left;   //p的右左parent->_right = subRL;//①if (subRL) //避免p的右左为空subRL->_parent = parent;subR->_left = parent;Node* ppnode = parent->_parent; //先记录p的父节点 以防p节点不是根节点parent->_parent = subR; //②if (parent == _root)//查看p是否为根节点{//p是根节点 则subR成为新的根接节点//再置一下subR父指针为空_root = subR;subR->_parent = nullptr;}else//p不是根 则p的父亲也需要正确的指向subR(判断原先的p是pp的左右孩子的哪一个){if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf = 0;subR->_bf = 0;}//右旋转//新节点插入较高左子树的左侧 简称左左//代表从次树的_root节点看左子树高1 且插入的节点在左子树的左侧//步骤: //①:p的左指针指向p原先的左孩子的右孩子//②:原先的p的左孩子的右指针指向p节点//代码逻辑和左旋转同理 不再赘述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;}//左右旋转->先左单旋再右单旋//新节点插入较高左子树的右侧//步骤 ://①:对p的左节点进行左单旋//②:再对p节点进行右单旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//由于在b树插入 在c树插入 或60本身就是插入的节点入 这三种情况双旋之后的树的bf不同//规律:根据插入节点的bf判断即可if (bf == -1)//代表在b树插入{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1)//代表在c树插入{subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}//else if (bf == 0)//代表subLR就是插入节点{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}//右左旋转//新节点插入较高右子树的左侧---右左:先右单旋再左单旋//步骤 ://①:对p的右节点进行右单旋//②:再对p节点进行左单旋//代码逻辑类似 不再赘述void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else{parent->_bf = 0;subR->_bf = 0;}}
c:中序函数

和二叉搜索树类似,不再赘述

void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << "[" << root->_bf << "]" << endl;_InOrder(root->_right);}//中序遍历void InOrder(){_InOrder(_root);}
d:查找函数

和二叉搜索树类似,不再赘述

//查找函数Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return NULL;}
e:IsBalence()函数(判断平衡函数)

我们写了以上这么多的函数实现,能证明其是一颗AVL树吗???

Q:我们中序遍历是升序,不就是AVL树吗

A: 中序遍历是升序,只能说明是二叉搜索树!

Q:打印一下每个节点的_bf值,没有一个_bf值大于1或者小于-1,不就行了?

A:万一bf本身就是错的呢,你怎么保证你的bf一定是对的?

Q:层序遍历打印,然后自己画二叉树?

A:你怎么知道打印出来的一串值,从哪里分割?怎么画二叉树?

正确答案:写一个IsBalence()函数(判断平衡函数)!来解决

该函数内部,会递归调用Height()函数去算每颗子树的高度差,顺便将此高度差和我们自己的_bf值对比一下,验证一下我们的准确性!

代码:

    int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}//高度函数int Height(){return _Height(_root);}bool _IsBalance(Node* root) {if (root == nullptr) {return true;}int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);// 检查当前节点是否平衡(左右子树高度差不超过1)if (abs(rightHeight - leftHeight) >= 2) {cout << root->_kv.first << " 不平衡" << endl;return false;}// 检查平衡因子是否正确(bf应等于右子树高度减左子树高度)if (rightHeight - leftHeight != root->_bf) {cout << root->_kv.first << " 平衡因子异常" << endl;return false;}// 递归检查左右子树return _IsBalance(root->_left) && _IsBalance(root->_right);}//是否平衡判断函数 bool IsBalance() //这是我们在类外调用的函数{int height = 0;return _IsBalance(_root, height);}

解释:该函数虽然达到了我们的要求,但是其并不是一个优秀的函数,其进行了大量的重复运算

对于一棵树,_IsBalance 会从根节点开始,逐层递归计算高度,导致子节点的高度被重复计算多次。例如,根节点的左右子树高度会被计算一次,而它们的子节点又会在各自的递归中被重复计算。

更优秀的判断平衡函数:

//更优秀的平衡函数bool _IsBalance(Node* root, int& height){if (root == nullptr){height = 0;return true;}int leftHeight = 0, rightHeight = 0;if (!_IsBalance(root->_left, leftHeight)|| !_IsBalance(root->_right, rightHeight)){return false;}if (abs(rightHeight - leftHeight) >= 2){cout << root->_kv.first << "不平衡" << endl;return false;}if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;return true;}//是否平衡判断函数bool IsBalance(){int height = 0;return _IsBalance(_root, height);}

 解释:该函数没有重复运算

抽象图如下: 

由上上图可知:

在递归过程中,通过 int& height 参数传递子树高度,避免重复计算。

计算高度和平衡性检查在一次后序遍历(左→右→根)中完成,时间复杂度降至 O(n)。

f:节点个数函数

过于简单不再赘述

//计算树的节点个数size_t Size(){return _Size(_root);}size_t _Size(Node* root){if (root == NULL)return 0;return _Size(root->_left)+ _Size(root->_right) + 1;}

没有实现删除,因为删除校招不考 考得一般是手撕旋转

三:AVL树代码

#pragma once
#include<assert.h>
#include<vector>//节点类
template<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) //pair类型的对象  存储k值和v值{}
};
//AVL类
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//AVL树不会存在相同的节点{return false;}}//走到这代表 找到了插入的位置 cur = new Node(kv);//先把该节点准备好//parent节点在之前是cur的父节点 也就是空节点的父亲//所以现在能将cur正确的链接到parent的正确方向if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//插入完成一定会影响parent节点的bf(只是看cur是p的哪一边 bf再++或--)//p的bf三种情况:bf 0 1/-1 2/-2//0:不会影响parent的祖先的bf 直接break//1/-1:树高度增加 会影响祖先的bf 所以更新完p的bf 再次循环继续更新上面的bf//2/-2:则需要旋转while (parent){//第一次更新p的bfif (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//对更新完的p的bf判断if (parent->_bf == 0){break;}//祖先节点依旧需要更新bf  再次循环else if (parent->_bf == 1 || parent->_bf == -1){cur = cur->_parent;parent = parent->_parent;}//需要旋转//进入旋转,咋代表cur和parent 之前已经循环找了 需要旋转的p节点//(该p节点的bf 2/-2)else if (parent->_bf == 2 || parent->_bf == -2){// 左旋转// 触发条件:p->bf为2 cur->_bf == 1if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}// 右旋转// 触发条件:parent->_bf == -2 && cur->_bf == -1else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}// 左右旋转// 触发条件:parent->_bf == -2 && cur->_bf == 1else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}//除此之外右左旋转else{RotateRL(parent);}break;}else{// 插入之前AVL树就有问题assert(false);//标记“不应执行到此处”}}//插入完成 则返回真return true;}//左旋转//新节点插入较高右子树的右侧---简称右右//代表从次树的_root节点看右子树高1 且插入的节点在右子树的右侧//步骤: //①:给到p的右指针指向原先p的右孩子的左子树//②:p的右子树的的左指针指向p节点void RotateL(Node* parent)//参数的p节点 就是bf为2的节点{Node* subR = parent->_right; //p的右Node* subRL = subR->_left;   //p的右左parent->_right = subRL;//①if (subRL) //避免p的右左为空subRL->_parent = parent;subR->_left = parent;Node* ppnode = parent->_parent; //先记录p的父节点 以防p节点不是根节点parent->_parent = subR; //②if (parent == _root)//查看p是否为根节点{//p是根节点 则subR成为新的根接节点//再置一下subR父指针为空_root = subR;subR->_parent = nullptr;}else//p不是根 则p的父亲也需要正确的指向subR(判断原先的p是pp的左右孩子的哪一个){if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf = 0;subR->_bf = 0;}//右旋转//新节点插入较高左子树的左侧 简称左左//代表从次树的_root节点看左子树高1 且插入的节点在左子树的左侧//步骤: //①:p的左指针指向p原先的左孩子的右孩子//②:原先的p的左孩子的右指针指向p节点//代码逻辑和左旋转同理 不再赘述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;}//左右旋转->先左单旋再右单旋//新节点插入较高左子树的右侧//步骤 ://①:对p的左节点进行左单旋//②:再对p节点进行右单旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//由于在b树插入 在c树插入 或60本身就是插入的节点入 这三种情况双旋之后的树的bf不同//规律:根据插入节点的bf判断即可if (bf == -1)//代表在b树插入{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1)//代表在c树插入{subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}//else if (bf == 0)//代表subLR就是插入节点{subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}//右左旋转//新节点插入较高右子树的左侧---右左:先右单旋再左单旋//步骤 ://①:对p的右节点进行右单旋//②:再对p节点进行左单旋//代码逻辑类似 不再赘述void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else{parent->_bf = 0;subR->_bf = 0;}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << "[" << root->_bf << "]" << endl;_InOrder(root->_right);}//中序遍历void InOrder(){_InOrder(_root);}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}//高度函数int Height(){return _Height(_root);}//bool _IsBalance(Node* root) {//	if (root == nullptr) {//		return true;//	}//	int leftHeight = Height(root->_left);//	int rightHeight = Height(root->_right);//	// 检查当前节点是否平衡(左右子树高度差不超过1)//	if (abs(rightHeight - leftHeight) >= 2) {//		cout << root->_kv.first << " 不平衡" << endl;//		return false;//	}//	// 检查平衡因子是否正确(bf应等于右子树高度减左子树高度)//	if (rightHeight - leftHeight != root->_bf) {//		cout << root->_kv.first << " 平衡因子异常" << endl;//		return false;//	}//	// 递归检查左右子树//	return _IsBalance(root->_left) && _IsBalance(root->_right);//}//更优秀的平衡函数bool _IsBalance(Node* root, int& height){if (root == nullptr){height = 0;return true;}int leftHeight = 0, rightHeight = 0;if (!_IsBalance(root->_left, leftHeight)|| !_IsBalance(root->_right, rightHeight)){return false;}if (abs(rightHeight - leftHeight) >= 2){cout << root->_kv.first << "不平衡" << endl;return false;}if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;return true;}//是否平衡判断函数bool IsBalance(){int height = 0;return _IsBalance(_root, height);}//计算树的节点个数size_t Size(){return _Size(_root);}size_t _Size(Node* root){if (root == NULL)return 0;return _Size(root->_left)+ _Size(root->_right) + 1;}//查找函数Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return NULL;}
private://成员变量_root根节点Node* _root = nullptr;
};

四:AVL树代码的测试

①:平衡函数的测试

void TestAVLTree1()
{//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };AVLTree<int, int> t;for (auto e : a){cout << e << "->" << t.IsBalance() << endl;}t.InOrder();cout << t.IsBalance() << endl;
}

运行结果:

完美~ 

②:代码总体的测试 一百万个数据

void TestAVLTree2()
{//将一百万个数放进v数组中const int N = 1000000;vector<int> v;v.reserve(N);//先预开辟一下srand(time(0));//for (size_t i = 0; i < N; i++){v.push_back(rand() + i);// 生成随机数并插入到 v 末尾; rand() 的范围有限(通常 0~32767),而 + i 可以扩展范围,降低重复概率。//cout << v.back() << endl;// 可取消注释以打印每个数}size_t begin2 = clock();//记录起始时间 begin2 AVLTree<int, int> t;for (auto e : v){//每个元素 e 作为 (key, value) 插入 AVLTreet.Insert(make_pair(e, e));//cout << "Insert:" << e << "->" << t.IsBalance() << endl;}size_t end2 = clock();//记录结束时间 end2//插入 100 万个元素的总 CPU 时间(时钟周期)。cout << "Insert:" << end2 - begin2 << endl;cout << "是否平衡:" << t.IsBalance() << endl;// 检查是否平衡cout << "Height:" << t.Height() << endl; // 输出树的高度cout << "Size:" << t.Size() << endl;// 输出树的节点数size_t begin1 = clock();//记录起始时间begin1// 在AVL树中查找所有已存在的值for (auto e : v){t.Find(e);}// 查找随机值(可能不存在)for (size_t i = 0; i < N; i++){t.Find((rand() + i));}size_t end1 = clock();//记录结束时间end1//end1 - begin1: 执行 200 万次查找(100 万成功 + 100 万随机)的 CPU 时间。cout << "Find:" << end1 - begin1 << endl;
}

运行结果:

Debug版本:

Release版本:

能看出,AVL树是一个十分优秀的结构!

为什么插入一百万数据 ,最终size只有六十三万,因为随机数有重复数据,AVL树底层是搜索二叉树,不会存在重复数据~~

相关文章:

手撕AVL树

引入&#xff1a;为何要有AVL树&#xff0c;二次搜索树有什么不足&#xff1f; 二叉搜索树有其自身的缺陷&#xff0c;假如往树中插入的元素有序或者接近有序&#xff0c;二叉搜索树就会退化成单支树&#xff0c;时间复杂度会退化成O(N)&#xff0c;因此产生了AVL树&#xff0c…...

Linux驱动开发练习案例

1 开发目标 1.1 架构图 操作系统&#xff1a;基于Linux5.10.10源码和STM32MP157开发板&#xff0c;完成tf-a(FSBL)、u-boot(SSBL)、uImage、dtbs的裁剪&#xff1b; 驱动层&#xff1a;为每个外设配置DTS并且单独封装外设驱动模块。其中电压ADC测试&#xff0c;采用linux内核…...

Redis 下载 — Ubuntu22.04稳定版,配置

官方文档 &#xff1a; https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/ Nano学习 &#xff1a; 【Linux环境下最先应该掌握的文本编辑器nano】https://www.bilibili.com/video/BV1p8411z7dJ?vd_source5ce003da2a16f44ea73ec9bbc30389e4 Redis配置…...

有没有可以帮助理解高数的视频或者书籍资料?

高数的学习是一个入门很高&#xff0c;但是一旦入门之后&#xff0c;就会变得比较简单的科目。 可是&#xff0c;我们应该怎么入门高数呢&#xff1f;在当年刚开始学习高数的时候&#xff0c;我也有过这样的困惑。 但是&#xff0c;后来我发现&#xff0c;我总是可以在经历一…...

了解拦截器

目录 什么是拦截器 拦截器的基本使用 拦截器的使用步骤 拦截器路径设置 拦截器执行流程 一、什么是拦截器 拦截器是Spring框架提供的核心功能之一&#xff0c;主要用来拦截用户的请求&#xff0c;在指定方法前后&#xff0c;根据业务需要执行预先设定的代码。 开发人员可以…...

Linux / Windows 下 Mamba / Vim / Vmamba 安装教程及安装包索引

目录 背景0. 前期环境查询/需求分析1. Linux 平台1.1 Mamba1.2 Vim1.3 Vmamba 2. Windows 平台2.1 Mamba2.1.1 Mamba 12.1.2 Mamba 2- 治标不治本- 终极版- 高算力版 2.2 Vim- 治标不治本- 终极版- 高算力版 2.3 Vmamba- 治标不治本- 终极版- 高算力版 3. Linux / Windows 双平…...

prism WPF 对话框

项目结构 1.创建对话框 用户控件 Views \ DialogView.xaml <UserControl x:Class"PrismWpfApp.Views.DialogView"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"…...

eventEmitter实现

没有做任何异常处理,简单模拟实现 事件对象的每一个事件都对应一个数组 /*__events {"事件1":[cb1,cb2],"事件2":[cb3,cb4],"事件3":[...],"事件4":[...],};*/class E{__events {};constructor(){}//注册监听回调on(type , callbac…...

Koordinator-NodeInfoCollector

Run 每秒执行一次 func (n *nodeInfoCollector) Run(stopCh <-chan struct{}) {go wait.Until(n.collectNodeInfo, n.collectInterval, stopCh) }collectNodeInfo() 采集node cpu信息采集node numa信息func (n *nodeInfoCollector) collectNodeInfo() {started := time.No…...

洛谷题单3-P5724 【深基4.习5】求极差 最大跨度值 最大值和最小值的差-python-流程图重构

题目描述 给出 n n n 和 n n n 个整数 a i a_i ai​&#xff0c;求这 n n n 个整数中的极差是什么。极差的意思是一组数中的最大值减去最小值的差。 输入格式 第一行输入一个正整数 n n n&#xff0c;表示整数个数。 第二行输入 n n n 个整数 a 1 , a 2 … a n a_1,…...

SignalR给特定User发送消息

1、背景 官网上SignalR的demo很详细&#xff0c;但是有个特别的问题&#xff0c;就是没有详细阐述如何给指定的用户发送消息。 2、解决思路 网上整体解决思路有三个&#xff1a; 1、最简单的方案&#xff0c;客户端连接SignalR的Hub时&#xff0c;只是简单的连接&#xff0c…...

新浪财经股票每天10点自动爬取

老规矩还是先分好三步&#xff0c;获取数据&#xff0c;解析数据&#xff0c;存储数据 因为股票是实时的&#xff0c;所以要加个cookie值&#xff0c;最好分线程或者爬取数据时等待爬取&#xff0c;不然会封ip 废话不多数&#xff0c;直接上代码 import matplotlib import r…...

【CSP】202403-1词频统计

文章目录 算法思路1. 数据结构选择2. 输入处理3. 统计出现的文章数4. 输出结果 代码示例代码优化 样例输入 4 3 5 1 2 3 2 1 1 1 3 2 2 2 2 3 2样例输出 2 3 3 6 2 2算法思路 1. 数据结构选择 vector<int>&#xff1a;用于存储每篇文章的单词列表&#xff08;可能包含…...

CentOs系统部署DNS服务

1. 安装 Bind 软件包 首先需要安装bind以及相关的工具包&#xff0c;在终端中执行以下命令&#xff1a; bash sudo yum install bind bind-utils -y2. 配置主配置文件 Bind 的主配置文件是/etc/named.conf&#xff0c;你可以使用文本编辑器&#xff08;如vim&#xff09;打开…...

LintCode第974题-求矩阵各节点的最短路径(以0为标准)

描述 给定一个由0和1组成的矩阵&#xff0c;求每个单元格最近的0的距离。 两个相邻细胞之间的距离是1。 给定矩阵的元素数不超过10,000。 在给定的矩阵中至少有一个0。 单元格在四个方向上相邻:上&#xff0c;下&#xff0c;左和右。 样例 例1: 输入: [[0,0,0],[0,0,0],[0…...

吴恩达深度学习复盘(6)神经网络的矢量化原理

矢量化基础是线性运算&#xff0c;这里先简单复习一下。线性基本运算基本没什么&#xff0c;大量使用的有点乘和叉乘。 基本例子 1. 矩阵的基本概念 - 矩阵可以看作是一个块或者二维数组&#xff0c;这是对矩阵的一个在计算机计算的直观描述。 2. 向量的点积&#xff08;内积…...

ISIS多区域配置

一、什么是ISIS多区域 ISIS&#xff08;Intermediate System to Intermediate System&#xff09;多区域是指网络被划分为多个逻辑区域&#xff08;Areas&#xff09;&#xff0c;不同区域之间通过特定的ISIS路由器&#xff08;Level-1-2&#xff09;进行路由交互。多区域设计提…...

The emulator process for AVD xxx has terminated

问题描述 离线环境下部署Android虚拟机&#xff0c;启动时报错The emulator process for AVD xxx has terminated&#xff0c;其中xxx为虚拟机名称。 解决过程 可先在C:\Users\admin\AppData\Local\Google\AndroidStudio2024.3\log目录下找到idea.log文件&#xff0c;其中记录…...

Haskell语言的区块链扩展性

Haskell语言的区块链扩展性研究 引言 区块链技术近年来在金融、供应链、物联网等多个领域取得了显著的进展。作为一种分布式账本技术&#xff0c;区块链的核心在于其去中心化、不可篡改和透明性。然而&#xff0c;随着应用的不断深入&#xff0c;区块链面临着可扩展性、性能、…...

第11/100节:三点估算

第11/100节&#xff1a;三点估算 三、完成某信息系统集成项目中的一个最基本的工作单元 A 所需的时间&#xff0c;乐观的估计需 8 天&#xff0c;悲观的估计需 38天&#xff0c;最可能的估计需 20 天&#xff0c;按照三点估算方法进行估算&#xff0c;项目的工期应该为&#xf…...

Tourists

一道圆方树恶心题&#xff0c;*3200&#xff0c;不知道为什么不评黑。 这道题很容易直接想到圆方树&#xff1a;因为两个操作如果在树上&#xff0c;都需要树链剖分 线段树维护。而将这么一个普通图转化为一棵树&#xff0c;也就只有圆方树这种形式了。 于是就可以综合使用圆…...

【动态规划】深入动态规划:连续子结构的算法剖析

文章目录 前言例题一、最大子数组和二、环形子数组的最大和三、 乘积最大子数组四、乘积为正数的最长子数组五、等差数列划分六、最长湍流子数组七、单词拆分八、环绕字符串中唯一的子字符串 结语 前言 什么是是动态规划连续子数组、子串系列算法问题? 连续子数组问题通常聚焦…...

结肠镜3D视频数据集-C3VD论文中文版

文章目录 标题作者摘要一、介绍1.1. 相关工作1.1.1. 内镜重建数据集1.1.2. 注册真实和虚拟内窥镜图像1.1.3. 2D-3D注册1.2. 贡献 二、方法2.1. 幻影模型生产2.2. 数据采集2.3. 注册流程概述2.3.1. 数据预处理2.3.2. 目标深度估计2.3.3. 渲染深度帧2.3.4. 边缘损失和优化 2.4. 模…...

封装自己的api签名sdk

api平台接口调用&#xff0c;需要通过签名去核对是不是有效的用户&#xff0c;&#xff0c;一般会给两个key&#xff0c;acceeKey 和 secretKey,第一个相当于用户名&#xff0c;第二个相当于密钥&#xff0c;&#xff0c;&#xff0c;前端通过一定的算法&#xff0c;&#xff0…...

ASP.NET Core Web API 中 HTTP状态码的分类及对应的返回方法

文章目录 前言一、HTTP状态码分类及常用方法二、具体返回方法示例1&#xff09; 2xx 成功类2&#xff09;4xx 客户端错误3&#xff09;5xx 服务器错误4&#xff09;其他特殊状态码 三、高级返回方式1&#xff09;使用 IActionResult 与 ActionResult<T>2&#xff09;统一…...

函数和模式化——python

一、模块和包 将一段代码保存为应该扩展名为.py 的文件&#xff0c;该文件就是模块。Python中的模块分为三种&#xff0c;分别为&#xff1a;内置模块、第三方模块和自定义模块。 内置模块和第三方模块又称为库内置模块&#xff0c;有 python 解释器自带&#xff0c;不用单独安…...

LeetCode 1817 查找用户活跃分钟数

深入剖析 LeetCode 用户活跃分钟数统计问题 一、题目详情 给定用户在 LeetCode 的操作日志&#xff0c;日志以二维整数数组logs表示&#xff0c;其中每个logs[i][IDi, timei]&#xff0c;意味着 ID 为IDi的用户在timei分钟时执行了某个操作。多个用户能够同时执行操作&#x…...

matlab从pytorch中导入LeNet-5网络框架

文章目录 一、Pytorch的LeNet-5网络准备二、保存用于导入matlab的model三、导入matlab四、用matlab训练这个导入的网络 这里演示从pytorch的LeNet-5网络导入到matlab中进行训练用。 一、Pytorch的LeNet-5网络准备 根据LeNet-5的结构图&#xff0c;我们可以写如下结构 import…...

网络:华为HCIA学习笔记:ICMP协议

ICMP&#xff08;Internet Control Message Protocol&#xff09;Internet控制消息协议 前言ICMPICMP重定向ICMP差错监测ICMP错误报告ICMP数据包格式ICMP消息类型和编码类型ICMP应用-PingICMP应用-Tracert 总结 前言 Internet控制消息协议ICMP (Internet Control Message Prot…...

Visio | 将(.vsdx)导出为更清楚/高质量的图片(.png) | 在Word里面的visio图

此时大家在用Visio画完图直接复制到word里面后&#xff0c;如果后期需要重新保存高清图片&#xff0c;但是此时图片在word&#xff0c;是不是很多人会选择直接crtlA截图复制&#xff0c;这样出来的图又不清晰又小&#xff0c;完全不符合你导的审美&#xff0c;接下来跟着我&…...

算法设计学习8

实验目的及要求&#xff1a; 通过深入学习树&#xff08;Tree&#xff09;和二叉树&#xff08;Binary Tree&#xff09;这两种重要的数据结构&#xff0c;掌握它们的基本概念、性质和操作&#xff0c;提高对树形结构的理解和应用能力。通过本实验&#xff0c;学生将深化对树和…...

Dive into Deep Learning - 2.4. Calculus (微积分)

Dive into Deep Learning - 2.4. Calculus {微积分} 1. Derivatives and Differentiation (导数和微分)1.1. Visualization Utilities 2. Chain Rule (链式法则)3. DiscussionReferences 2.4. Calculus https://d2l.ai/chapter_preliminaries/calculus.html For a long time, …...

kotlin中const 和val的区别

在 Kotlin 中&#xff0c;const 和 val 都是用来声明常量的&#xff0c;但它们的使用场景和功能有所不同&#xff1a; 1. val: val 用于声明只读变量&#xff0c;也就是不可修改的变量&#xff08;类似于 Java 中的 final 变量&#xff09;。它可以是任何类型&#xff0c;包括…...

Webpack中loader的作用。

文章目录 前言1. 处理样式文件2. 处理 JavaScript 文件3. 处理其他文件总结 前言 在 Webpack 中&#xff0c;Loader 是用于对模块的源代码进行转换的工具&#xff0c;它能够将不同类型的文件&#xff08;如 CSS、图片、字体、TypeScript 等&#xff09;转换为有效的 JavaScrip…...

C++ 极简常用内容

C 极简常用内容 1. 类与对象 定义&#xff1a;封装数据&#xff08;成员变量&#xff09;和行为&#xff08;成员函数&#xff09;的自定义类型。 Demo&#xff1a; class Car { public:string brand;void drive() { cout << brand << " is moving." …...

如何在windows 环境、且没有显卡的情况下用python跑通从ModelScope下载的大模型的调用

文章目录 背景介绍源代码&#xff1a;安装调试过程1.设置第三方镜像源2.预先安装&#xff1a;3.在python中创建代码&#xff1a;4.最终修改程序,将device_map从“cuda”改成“auto”&#xff0c;大模型调用1.5B&#xff08;1___5B)的5.最终跑出结果解释&#xff1a;示例&#x…...

MINIQMT学习课程Day10

开始获取股票数据课程的学习&#xff1a; 获取qmt账号的持仓情况后&#xff0c;我们进入下一步&#xff0c;如何获得当前账号的委托状况 还是之前的步骤&#xff0c;打开qmt&#xff0c;选择独立交易&#xff0c; 之后使用pycharm&#xff0c;编写py文件 导入包&#xff1a…...

如何彻底关闭Windows 10中的Xbox游戏栏

一、打工人的困扰&#xff1a;不速之客“Game Bar” 在日常工作中&#xff0c;许多使用Windows 10的用户常常被一个不起眼却频频打扰的系统功能困扰&#xff0c;那就是“Xbox游戏栏”&#xff08;Game Bar&#xff09;。当你正在专注处理紧急的Excel表格或准备PPT汇报&#xf…...

26考研资料分享考研资料合集 百度网盘(仅供参考学习)

考研资料分享考研资料合集 百度网盘&#xff08;仅供参考学习&#xff09; 通过网盘分享的文件&#xff1a;2026考研英语数学政治最新等3个文件 链接1: https://pan.baidu.com/s/1JXBI9ROng4KAWHoaUHpkug?pwdjydb 提取码: jydb 链接2:https://pan.baidu.com/s/1a…...

59.基于ssm和vue学生考试成绩管理系统

目录 1.系统的受众说明 2.系统关键技术 2.1 java技术 2.2 MYSQL数据库 2.3 B/S结构 3.系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2经济可行性 3.2 系统性能分析 3.3 系统功能分析 3.5系统流程分析 3.5.1登录流程 3.5.2注册流程 3.5.3添加信息流程 3.5.4删…...

常见的ETL工具分类整理

一、开源ETL工具 ‌Kettle&#xff08;Pentaho Data Integration&#xff09;--Spoon‌ 设计及架构&#xff1a;面向数据仓库建模的传统ETL工具。使用方式&#xff1a;C/S客户端模式&#xff0c;开发和生产环境需要独立部署&#xff0c;任务编写、调试、修改都在本地。底层架构…...

【leetcode100】数组中的第K个最大元素

1、题目描述 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输入: [3,2,…...

markdown语法学习

三化markdown语法 研究对象系统化全局化结构化markdown语法富文本字体样式*斜体*&#xff0c;主题样式#&#xff0c;表格样式||&#xff0c;代码块样式&#xff0c;待办样式- [ ]样式之间互相协作&#xff0c;互不冲突 待办 斜体 加粗 标题 删除线 public class{ //代码块 …...

Linux_4

开始学习ssh工具 在做开发的时候,肯定不止一台服务器,那么假设每台服务器都是Linux服务器,要在服务器上操作就需要登入终端,即Terminal。ssh的作用就是可以通过一个服务器登陆上其他的服务器。 登陆到哪个服务器看到的就是哪个服务器的终端terminal。 ssh登陆 ssh user@…...

Netty——连接超时 与 断开重连

文章目录 1. 处理连接超时和断开重连的原因2. 处理连接超时和断开重连的方法2.1 处理连接超时2.1.1 步骤一&#xff1a;配置连接超时时间2.1.2 步骤二&#xff1a;监听连接结果 2.2 处理断开重连2.2.1 步骤一&#xff1a;监听连接断开事件2.2.2 步骤二&#xff1a;实现重连逻辑…...

linux 进程/线程设置核亲和性

1&#xff0c;线程绑定内核 #include <pthread.h> #include <stdio.h> #include <stdlib.h> // 定义一个函数&#xff0c;用于设置线程的CPU亲和性 void set_thread_affinity(pthread_t thread, int cpu_id) { cpu_set_t cpuset; int s; // 清空CPU集…...

前端页面鼠标移动监控(鼠标运动、鼠标监控)鼠标节流处理、throttle、限制触发频率(setTimeout、clearInterval)

文章目录 使用lodashjs库手动实现节流&#xff08;通过判断之前设定的定时器setTimeout是否存在&#xff09; 使用lodashjs库 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Com…...

【MySQL基础-21】MySQL事务机制详解:原理、实现与最佳实践

在现代数据库系统中&#xff0c;事务机制是确保数据一致性和完整性的核心技术。MySQL作为最流行的开源关系型数据库之一&#xff0c;其事务实现机制对于开发者而言至关重要。本文将深入探讨MySQL的事务机制&#xff0c;包括核心概念、实现原理、隔离级别以及实际应用中的最佳实…...

Transformer+BO-SVM时间序列预测(Matlab)

TransformerBO-SVM时间序列预测&#xff08;Matlab&#xff09; 目录 TransformerBO-SVM时间序列预测&#xff08;Matlab&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 普通的单变量时序已经用腻了&#xff0c;审稿人也看烦了&#xff0c;本期推出一期高创…...

Python循环控制语句

1. 循环类型概述 Python提供两种主要的循环结构&#xff1a; while循环 - 在条件为真时重复执行for循环 - 遍历序列中的元素 2. while循环 基本语法 while 条件表达式:循环体代码示例 count 0 while count < 5:print(f"这是第{count1}次循环")count 13. f…...