【数据结构】AVL树(图文解析 + 代码实现)

目录

1、AVL树的概念

2、AVL树结点的定义

3、AVL树的插入

4、AVL树的旋转

4.1 左单旋

4.2 右单旋

4.3 右左双旋

4.4 左右双旋

5、AVL树的验证

6、AVL树的性能

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

1、AVL树的概念

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

为什么不将树调整为左右子树的高度相等呢?因为有些结点数量下(如2和4个结点),做不到左右子树高度相等,所以最好的平衡二叉树就是高度不超过1

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

上面这棵树的平衡因子是用右子树高度-左子树高度,这不是一定的,也可以反过来,并没有严格规定,这篇文章中的平衡因子都是用右子树高度-左子树高度的

因为AVL树每个结点的左右子树高度差不超过1,所以平衡因子只有在-1、0、1是才是正常的

2、AVL树结点的定义

我们这里实现的是KV模型的AVL树,为了与map对应,两个值是以pair的形式存储。并且每个结点中还要增加一个平衡因子。当插入新结点时,为了检查路径上是否出现异常的平衡因子,还要增加一个指向父亲结点的指针,另外原来还有指向左右孩子的指针,称为三叉链结构

template<class K,class V>
struct AVLTreeNode
{pair<K, V> _kv;// 三叉链AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;int _bf; // 平衡因子AVLTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};

根结点的_parent置为nullptr 

3、AVL树的插入

向AVL树中插入一个新结点时,插入的操作是和二叉搜索树一致的,只是插入一个结点后,需要更新一下这个结点到这棵树的根节点路径上部分结点的平衡因子,若是平衡因子出现异常,则需要进行旋转操作。

插入结点,会影响部分祖先结点的平衡因子。因为这里的平衡因子 = 右子树高度 - 左子树高度

结点插入在左子树,其父亲的平衡因子--

结点插入在右子树,其父亲的平衡因子++

是否需要继续往上更新祖先,要看parent所在子树的高度是否发生了变化,此时有3种情况:

1. 更新后parent的平衡因子变成0

说明没插入时parent的平衡因子是-1/1,一边高一边低,插入后,变成两边一样高,parent所在子树的高度没有发生变化,所以不需要向上更新

2. 更新后parent的平衡因子变成-1/1

说明没插入时parent的平衡因子是0,两边一样高,插入后,变成一边高一边低,parent所在子树的高度变高了,所以需要继续向上更新

3. 更新后parent的平衡因子变成-2/2

说明没插入时parent的平衡因子是-1/1,插入结点插入在高的那一边,进一步加剧了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;}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--;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){// 不平衡了,旋转处理}else{assert(false);}}return true;
}

4、AVL树的旋转

4.1 左单旋

当新结点插入到右子树的右侧,需要进行左单旋 ----- 右右:左单旋

这里面h>=0,表示a、b、c是高度为h的AVL子树

左单旋的标志就是出现异常的结点的平衡因子为2,并且其右子树的平衡因子为1,那么这个时候就进行左单旋。

左单旋步骤:

1. 将subRL变成parent的右子树

2. 将parent变成subR的左子树

3. 建立subR与parent的父亲结点parentParent的关系

4. 更新parent和subR的平衡因子 

完成代码时,除了要完成每个结点的链接关系,还需要注意重新链接后结点父亲的变化和平衡因子的变化。其中平衡因子只有30和60这两个结点有变化,因为要改变平衡因子,需要改变左右子树的高度,并且这两个结点的平衡因子都变成了0。注意,subRL是有可能为nullptr的,当h等于0时,所以修改其父亲结点时需要判断一下。

void RotateL(Node* parent) // 左单旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;// 1. 将subRL变成parent的右子树parent->_right = subRL;if (subRL)subRL->_parent = parent;// 2. 将parent变成subR的左子树subR->_left = parent;parent->_parent = subR;// 3. 建立subR与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}// 4. 更新parent和subR的平衡因子 parent->_bf = subR->_bf = 0;
}

4.2 右单旋

当新插入的结点在左子树的左侧,需要进行右单旋 ----- 左左:右单旋

右单旋的标志就是出现异常的结点的平衡因子为-2,并且其左子树的平衡因子为-1,那么这个时候就进行左单旋。 

右单旋的步骤:

1. 将subLR变成parent的左子树

2. 将parent变成subL的右子树

3. 建立subL与parent的父亲结点parentParent的关系

4. 更新parent和subL的平衡因子 

void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;// 1. 将subLR变成parent的左子树parent->_left = subLR;if (subLR)subLR->_parent = parent;// 2. 将parent变成subL的右子树subL->_right = parent;parent->_parent = subL;// 3. 建立subL与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent)parentParent->_left = subL;elseparentParent->_right = subL;subL->_parent = parentParent;}// 4. 更新parent和subL的平衡因子 parent->_bf = subL->_bf = 0;
}

4.3 右左双旋

当新插入的结点在右子树的左侧,需要先进行右单旋,再进行左单旋 ----- 右左:右左双旋

右左双旋的标志是出现异常的结点的平衡因子是2,其右子树的平衡因子是-1

第一种情况:在右子树的左子树的右子树插入(即c子树插入)

  第二种情况:在右子树的左子树的左子树插入(即b子树插入)

这两种情况都是先进行右单旋,然后进行左单旋,只是旋转完成后,个别结点的平衡因子不同。如何区分是在b插入还是c插入呢?

当h > 0时

1. 插入结点后,若右子树的左子树这个结点的平衡因子是1,则是在c插入的

2. 插入结点后,若右子树的左子树这个结点的平衡因子是-1,则是在b插入的

上面两幅图就是h > 0的两种情况的图

当h == 0 时

3. 插入之前连60这个结点都没有,60这个结点自己就是新增,此时60这个结点的平衡因子是0

右左单旋步骤:

1、计算subRL的平衡因子

2、对subR进行右单旋

3、对parent进行左单旋

4、更新parent、subR、subRL的平衡因子

void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;// 1、计算subRL的平衡因子int bf = subRL->_bf;// 2、对subR进行右单旋RotateR(subR);// 3、对parent进行左单旋RotateL(parent);// 4、更新parent、subR、subRL的平衡因子if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}
}

4.4 左右双旋

当新插入的结点在左子树的右侧,需要先进行左单旋,再进行右单旋 ----- 左右:左右双旋

左右双旋的标志是出现异常的结点的平衡因子是-2,其左子树的平衡因子是1

第一种情况:在左子树的右子树的左子树插入(即b子树插入)

第二种情况:在左子树的右子树的右子树插入(即c子树插入)

第三种情况:当h == 0时

左右单旋的步骤:

1. 计算subLR的平衡因子

2. 对subL进行左单旋

3. 对parent进行右单旋

4. 更新parent、subR、subRL的平衡因子

void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;// 1. 计算subLR的平衡因子int bf = subLR->_bf;// 2. 对subL进行左单旋RotateL(subL);// 3. 对parent进行右单旋RotateR(parent);// 4. 更新parent、subR、subRL的平衡因子if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}
}

会发现同号单旋,异号双旋

所以此时插入操作的代码为

bool Insert(const pair<K, V>& kv)
{// 若_root为空,直接让新插入的结点变成根if (_root == nullptr){_root = new Node(kv);return true;}// 若_root不为空,按二叉搜索树的规则找到插入的位置,同时也要记录父亲结点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--;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 && parent->_right->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && parent->_left->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && parent->_right->_bf == -1){RotateRL(parent);}else{RotateLR(parent);}break; // 旋转完后,一定平衡了,所以可以跳出循环}else{assert(false);}}return true;
}

注意,旋转完成之后,这颗子树的父亲结点的平衡因子一定是0,所以可以直接跳出循环,不需要继续向上更新平衡因子

5、AVL树的验证

在前面,我们已经实现了插入操作,那么按照这个插入函数获得的真的是一颗AVL树吗?

所以,我们需要进行验证。验证只需要计算每个结点的左右树高,判断左右树高的差值小于2即可

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;
}
bool _IsBalanceTree(Node* root)
{// 空树也是AVL树if (root == nullptr) return true;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (abs(diff) >= 2 || root->_bf != diff)return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root -> _right);
}

这两个函数需要写在AVL树内部,因为用到了_root

6、AVL树的性能

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

template<class K,class V>
struct AVLTreeNode
{pair<K, V> _kv;// 三叉链AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;int _bf; // 平衡因子AVLTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};
template<class K,class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:AVLTree() = default;~AVLTree(){Destory(_root);_root = nullptr;}AVLTree(const AVLTree<K,V>& kv){_root = Copy(kv._root);}AVLTree operator=(AVLTree<K, V> kv){swap(_root, kv._root);}bool Insert(const pair<K, V>& kv){// 若_root为空,直接让新插入的结点变成根if (_root == nullptr){_root = new Node(kv);return true;}// 若_root不为空,按二叉搜索树的规则找到插入的位置,同时也要记录父亲结点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--;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 && parent->_right->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && parent->_left->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && parent->_right->_bf == -1){RotateRL(parent);}else{RotateLR(parent);}break; // 旋转完后,一定平衡了,所以可以跳出循环}else{assert(false);}}return true;}void InOrder(){_InOrder(_root);}bool IsBalanceTree(){return _IsBalanceTree(_root);}int Height(){return _Height(_root);}int Size(){_Size(_root);}
private:int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}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;}bool _IsBalanceTree(Node* root){// 空树也是AVL树if (root == nullptr) return true;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (abs(diff) >= 2 || root->_bf != diff)return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root -> _right);}void RotateL(Node* parent) // 左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;// 1. 将subRL变成parent的右子树parent->_right = subRL;if (subRL)subRL->_parent = parent;// 2. 将parent变成subR的左子树subR->_left = parent;parent->_parent = subR;// 3. 建立subR与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}// 4. 更新parent和subR的平衡因子?parent->_bf = subR->_bf = 0;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;// 1. 将subLR变成parent的左子树parent->_left = subLR;if (subLR)subLR->_parent = parent;// 2. 将parent变成subL的右子树subL->_right = parent;parent->_parent = subL;// 3.?建立subL与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent)parentParent->_left = subL;elseparentParent->_right = subL;subL->_parent = parentParent;}// 4.更新parent和subL的平衡因子parent->_bf = subL->_bf = 0;}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;// 1、计算subRL的平衡因子int bf = subRL->_bf;// 2、对subR进行右单旋RotateR(subR);// 3、对parent进行左单旋RotateL(parent);// 4、更新parent、subR、subRL的平衡因子if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;// 1. 计算subLR的平衡因子int bf = subLR->_bf;// 2. 对subL进行左单旋RotateL(subL);// 3. 对parent进行右单旋RotateR(parent);// 4.?更新parent、subR、subRL的平衡因子if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}}void Destory(Node* root){if (root == nullptr) return;Destory(root->_left);Destory(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr) return nullptr;Node* newNode = new Node(root->_kv);newNode->_left = Copy(root->_left);newNode->_right = Copy(root->_right);return newNode;}void _InOrder(Node* _root){if (_root == nullptr) return;_InOrder(_root->_left);cout << _root->_kv.first << "--" << _root->_kv.second << "--" << _root->_bf << endl;_InOrder(_root->_right);}Node* _root;
};

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【AI大模型】程序员AI的未来——Copilot还是Claude3.5 Sonnet?

近期&#xff0c;Anthropic发布了Claude 3.5 的“大杯”模型 —— Claude 3.5 Sonnet&#xff01; 这次发布的 Sonnet 代表意大利的“十四行诗”&#xff0c;结构复杂&#xff0c;在智能水平、功能多样性和处理能力上都有所提升&#xff0c;能够应对更复杂的认知任务&#xff…

解决VS2019+Qt联合开发双击Resource Files弹不出资源编辑器问题

目录 一、右键Resource.qrc文件 二、选择打开方式 三、鼠标选择Qt Resource Editor&#xff0c;并设置为默认值 四、最后点击确定&#xff0c;再次双击qrc文件&#xff0c;成功打开 最近在开发中&#xff0c;遇见一个问题&#xff0c;在VS联合Qt开发时&#xff0c;需要添加…

前后端分离项目部署,vue--nagix发布部署,.net--API发布部署。

目录 Nginx免安装部署文件包准备一、vue前端部署1、修改http.js2、npm run build 编译项目3、解压Nginx免安装,修改nginx.conf二、.net后端发布部署1、编辑appsetting.json,配置跨域请求2、配置WebApi,点击发布3、配置文件发布到那个文件夹4、配置发布相关选项5、点击保存,…

推荐一款基于 SpringBoot2 的后台管理系统脚手架,非常轻量简单(附源码)

前言 在现代软件开发中&#xff0c;后台管理系统是企业数字化转型的关键组成部分。然而&#xff0c;现有软件常常存在一些痛点&#xff0c;如复杂的权限管理、缺乏灵活的工作流配置、监控和日志功能不完善等。此外&#xff0c;许多系统study 成本高&#xff0c;依赖关系复杂&a…

VS2015加断点(红色),修改过后,断点变为白色不能命中

实际这个问题是因为&#xff1a;源文件和原始版本不同。解决方法有二&#xff1a; 一&#xff0c;在断点上右键&#xff0c;选择“位置”》勾选”允许源代码与原始版本不同&#xff1b; 二&#xff0c;点击菜单栏“调试”》“选项和设置”》“常规”》去掉“要求源文件与原始…

MINE:Mutual Information Neural Estimation

Mutual Information Neural Estimation 摘要 文章认为高维连续随机变量之间的互信息可以通过在神经网络上梯度下降优化得到。 文中提出了互信息估计器(Mutual Information Neural Estimator),它在维度和 样本大小上都是可伸缩的&#xff0c;可以通过反向传播训练的&#xff0…

OCC 布尔运算

目录 一、裁剪原理 二、使用详解 1. 差集 (Cut) 2. 联合 (Fuse/Union) 3. 交集 (Common/Intersection) 三、例子 1、两个盒子裁剪 2、任意面裁剪 四、总结 一、裁剪原理 OpenCASCADE (OCC) 中的裁剪(Boolean Cut)原理主要基于布尔运算。布尔运算是计算机图形学中的…

力扣第二十四题——两两交换链表中的节点

内容介绍 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xff…

Python-numpy基础--------2

1.full()创建函数 目录 1.full()创建函数 2.创建单位矩阵 3.linspace创建 4.logspace 创建 5.二维数组的索引和切片&#xff1a; 1.索引直接获取 在NumPy中&#xff0c;full() 函数用于创建一个给定形状、类型的新数组&#xff0c;并用指定的值填充这个数组。这个函数非…

数模·插值和拟合算法

插值 将离散的点连成曲线或者线段的一种方法 题目中有"任意时刻任意的量"时使用插值&#xff0c;因为插值一定经过样本点 插值函数的概念 插值函数与样本离散的点一一重合 插值函数往往有多个区间&#xff0c;多个区间插值函数样态不完全一样&#xff0c;简单来说就…

AWS监控工具,监控性能指标

执行AWS监视是为了跟踪在AWS环境中主动运行的应用程序工作负载和资源&#xff0c;AWS监视器跟踪各种AWS云指标&#xff0c;以帮助提高在其上运行的应用程序的整体性能。 借助阈值突破警报系统&#xff0c;AWS应用程序监控在识别性能瓶颈来源方面起着至关重要的作用&#xff0c…

linux版mysql8配置表名不区分大小写

mysql8的安装步骤可参考&#xff1a; mysql8的安装步骤 如果在安装mysql8&#xff0c;初始化之前&#xff0c;没有在my.cnf配置忽略大小写配置&#xff1a; [mysqld] lower_case_table_names1我们就需要重新初始化mysql 1 备份数据库文件 2 停止mysql服务 systemctl stop …

HTML+CSS+JS扫雷(可自定义雷数,大小,可插旗)

源代码在最后面 点赞❤️ 关注⭐️谢谢&#x1f61c; 实现功能 随机扫雷自定义地雷数、游戏棋盘大小插旗 效果图&#xff08;部分图片&#xff09; 源代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><m…

学习TS-类

class Car{ // 字段 name : String; age : Number; //静态 静态成员可以直接通过类名调用 static shooch:string; // 构造函数 // 构造函数会在对象创建时调用 constructor(name:string,age:Number){ //在实例方法中&#xff0c;this就表示当前当前的实例 //在构造函数中当前对…

kettle从入门到精通 第七十九课 ETL之kettle kettle读取数据库BLOB字段转换为文件

上一课我们讲解了如何将文件以二进制流的方式写入数据库&#xff0c;本节课我们一起学习下如何将二进制数据读取为文件。 1、将二进制流转换为文件这里主要用到了步骤【文本文件输出】。表输入步骤从表中读取blob字段&#xff0c;java代码定义二进制流转换为文件的全路径&#…

openmv学习笔记(24电赛笔记)

#opemv代码烧录清除详解 openmv的代码脱离IDE运行程序&#xff0c;只需要在IDE中将代码烧录道flash里面&#xff0c;断开IDE连接&#xff0c;上电之后&#xff0c;会自动执行main.py中的程序&#xff0c;IDE烧录的时候&#xff0c;会默认将程序后缀保存为 .py文件。 ​​​​​…

SpringBoot3整合Druid报错Cannot load driver class: org.h2.Driver

报错显示springboot自带的H2数据库报错&#xff0c;其实是因为druid并未加载进去。如果你其它配置都没问题的话&#xff0c;请检查druid的依赖是什么版本的&#xff0c;因为springboot3刚开始是不支持druid的。 方案一&#xff1a; 即需要手动在resources目录下创建META-INF/s…

利用request + BeautifulSoup 模块批量爬取内容,实现批量获取书名对应的豆瓣评分

文章目录 代码代码解释控制台输出结果 代码 #-*- coding:utf-8 -*- from bs4 import BeautifulSoup import requests, time, jsonheaders {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.39…

实际生活中网段不通的典型分析及处理方案

关于端口&#xff1a; 应用层&#xff1a; FTP TELNET SMTP DNS TFTP SNMP 端口号&#xff1a; 21 23 25 53 69 161 传输层&#xff1a; TCP UDP&#xff08;DNS两个都占…

《Java初阶数据结构》----3.<线性表---LinkedList与链表>

目录 前言 一、链表的简介 1.1链表的概念 1.2链表的八种结构 重点掌握两种 1.3单链表的常见方法 三、单链表的模拟实现 四、LinkedList的模拟实现&#xff08;双链表&#xff09; 4.1 什么是LinkedList 4.2LinkedList的使用 五、ArrayList和LinkedList的区别 前言 …