【C++ 第十三章】AVL 二叉平衡树

1. AVL树的概念

        普通二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。

        因此,两位俄罗斯的数学家 G.M.Adelson-Velskii 和 E.M.Landis 在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

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

子树高度:即 从根节点开始算,一直到最后一层叶子节点 中,一共多少树层

我们本文默认 左右子树高度之差 = 右子树高度 - 左子树高度

(右减左 和 左减右 都一样的,意义一样)

如果一棵二叉搜索树是高度平衡的,它就是AVL树。

如果它有 N 个结点,其高度可保持在 logN 层 ,搜索时间复杂度O(logN)。

下图中数字为  左右子树高度之差


2. AVL树节点 类

节点默认  key/value 键值对模型

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

3. AVL树的插入

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

1. 按照二叉搜索树的方式插入新节点

2. 调整节点的平衡因子

3.1 按照二叉搜索树的方式插入新节点

// 插入
Node* insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);return _root;}Node* cur = _root;Node* parent = cur;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;}}// 在 cur 的位置插入该节点cur = new Node(kv);if ((parent->_kv).first > kv.first) parent->_left = cur;else  parent->_right = cur;cur->_parent = parent; // 每个节点连接父节点// 更新平衡因子// .....return _root;
}

3.2 调整节点的平衡因子

前面提过 左右子树高度之差 即为 平衡因子


插入一个节点,会影响子树的高度,因此影响一系列 平衡因子

当前插入一个节点 cur ,其 父节点 parent 和 祖先节点 的 平衡因子 都可能被影响

⭐例如:

1、当在 节点 8 的左边插入一个 节点,节点 8 的 平衡因子变成 0,其他的节点不变

2、当在 节点 4 的右边插入一个 节点,节点 4 的 平衡因子变成 1,节点 3 的 平衡因子变成 0,其他的节点不变


插入节点,会影响部分祖先节点的平衡因子

⭐(1)更新平衡因子

插入在左子树,平衡因子--

插入在右子树,平衡因子++

⭐(2)处理 平衡因子 的 几种情况:

是否继续往上更新祖先,要看 parent 所在子树的高度是否变化

🐵1、 parent 的平衡因子  bf == 0

说明 parent 的平衡因子更新前是 1 or -1,

插入节点插入矮的那边 parent 所在子树的高度不变,

说明刚好平衡,不需要继续往上更新

🐵2. parent 的平衡因子 bf == 1 or -1

说明 parent 的平衡因子更新前是 0:即两边高度一样,子树平衡

插入节点插入在任意一边 parent 所在的子树高度都会变化了

说明刚好打破平衡,继续向上更新

🐵3. parent 的平衡因子== 2 or -2

说明parent的平衡因子更新前是 1 or -1,插入节点插入在高的那边

进一步加剧了parent所在的子树的不平衡,已经违反违规了,

子树失衡,需要旋转处理


🐵4. 其他情况:都是不合理的,直接报错


 

注意看注释理解

// 更新平衡因子
while (parent) {// 插入在左边,父亲平衡因子 减减// 插入在右边,父亲平衡因子 加加if (cur == parent->_left) parent->_bf--;else if (cur == parent->_right) parent->_bf++;// 若 bf == 0:说明刚好平衡// 若 bf == 1 or -1:说明刚好打破平衡,但还算做平衡,继续向上更新// 若 bf == 2 or -2:说明失衡,旋转// 其他:其他情况都是不合理的,直接报错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);
}

3.3 旋转逻辑

前面 3.2 节中提到,当 插入一个节点 parent 的平衡因子== 2 or -2 时,应该执行旋转

旋转有 四种类型:

RR 型、LL 型 、LR型、RL型

⭐RR 型旋转:左单旋

添加 节点 7 后:节点 3 的平衡因子变成 2

则要对 节点 3 执行旋转操作:因为 节点 5 是 右孩子,节点 6 也是 右孩子 

双 R,即 RR型,向左旋转一次


旋转逻辑 如图示:


动图演示 旋转:

(注:动图取材取自 B站up主:蓝不过海呀 (若侵权即删))

代码示例
// RR型:左单旋
void rotateRR(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;  // subRL 是有可能为 空的// 2、parent变成subR的左孩子subR->_left = parent;parent->_parent = subR;// 3、subR变成当前子树的根if (parentParent) {if (parent == parentParent->_right)parentParent->_right = subR;else parentParent->_left = subR;subR->_parent = parentParent;}// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空else {_root = subR;subR->_parent = nullptr;}// 单独处理 平衡因子subR->_bf = 0;parent->_bf = 0;
}

⭐LL 型旋转:右单旋

添加 节点 2 后:节点 5 的平衡因子变成 -2

节点 5 :左子树高度 = 3 ,右子树高度 = 1 ,则节点 5 的平衡因子 = -2,需要旋转

旋转逻辑 如图示:


动图演示 旋转:

(注:动图取材取自 B站up主:蓝不过海呀 (若侵权即删))

代码示例

其实代码逻辑 和 上面的 RR型就是刚好 镜像相反

// LL型:右单旋
void rotateLL(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;  // subRL 是有可能为 空的 // 2、parent变成subL的右孩子subL->_right = parent;parent->_parent = subL;// 3、subL变成当前子树的根if (parentParent) {if (parent == parentParent->_right)parentParent->_right = subL;else parentParent->_left = subL;subL->_parent = parentParent;}// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空else {_root = subL;subL->_parent = nullptr;}subL->_bf = 0;parent->_bf = 0;
}

⭐LR 型旋转:subL 先 左旋,parent 再 右旋(先 L 后 R)

旋转逻辑 如图示:


动图演示 旋转:

(注:动图取材取自 B站up主:蓝不过海呀 (若侵权即删))

代码示例

双旋,其实直接复用 前面两个单旋的函数即可,无需自己再实现

关于平衡因子的更新:因为单旋函数中,都是直接将 平衡因子置为 0

       而双旋的平衡因子会改变,因此需要 先旋转后,再添加处理平衡因子的逻辑

各个节点平衡因子的更新数值如何确定:这个直接看双旋后各个节点的平衡因子为多少即可,是固定值

// LR 型:subL 先 左旋, parent 右旋
void rotateLR(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;rotateRR(parent->_left);rotateLL(parent);// 双旋后,各个节点平衡因子的更新if (bf == -1) {parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1) {parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == 0) {parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else assert(false);
}

⭐RL 型旋转:subR 先 右旋,parent 再 左旋(先 R 后 L)

这个原理也就是上面的 LR 型旋转 刚好镜像相反

动图演示 旋转:

(注:动图取材取自 B站up主:蓝不过海呀 (若侵权即删))

代码示例
// RL 型:subR 先 右旋, parent 左旋
void rotateRL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;rotateLL(parent->_right);rotateRR(parent);// 双旋后,各个节点平衡因子的更新if (bf == -1) {parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1) {parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 0) {parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else assert(false);
}

⭐关于 双旋 中 平衡因子的更新逻辑

上面 LR 型旋转 和  RL 型旋转 在 复用单旋的函数后,还需要对 平衡因子进行处理更新

我们以 LR 型旋转为例,解释为什么 可以根据 subRL 的 平衡因子的数值 来区分几种情况

int bf = subLR->_bf;// 双旋后,各个节点平衡因子的更新
if (bf == -1) {parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;
}
else if (bf == 1) {parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;
}
else if (bf == 0) {parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;
}
else assert(false);

下面三种情况:

初始状态下,parent 和 subL 都是固定相同的唯一不同的是 subLR 的,因此可以以 subLR 作为区分指标

情况一:bf == -1

情况二:bf == 1

情况三:bf == 0




3.4 旋转 逻辑的代码运用  与  相关总结

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

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

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

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

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

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

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

旋转完成后,原 parent 为根的子树个高度降低,已经平衡,不需要再向上更新(因此在旋转逻辑代码中,旋转完直接 break 退出循环)

⭐提炼上面的总结,转换为代码中条件为同号单旋,异号双旋

(注 : bf_P 即为 parent 的平衡因子, bf_C 即为 cur 的平衡因子)

bf_P == 2    &&     bf_C  == 1   :RR型 左单旋

bf_P == -2    &&     bf_C  == -1   :LL型 右单旋

bf_P == -2    &&     bf_C  == 1   :LR型 左右双旋

bf_P == 2    &&     bf_C  == -1   :RL型 右左双旋

更新平衡因子,当 平衡因子 == 2 or -2 时,就要旋转

// 更新平衡因子
while (parent) {if (cur == parent->_left) parent->_bf--;else if (cur == parent->_right) parent->_bf++;// 若 bf == 0:说明刚好平衡// 若 bf == 1 or -1:说明刚好打破平衡,但还算做平衡,继续向上更新// 若 bf == 2 or -2:说明失衡,旋转// 其他:其他情况都是不合理的,直接报错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) {// 旋转:同号单旋,异号双旋// RR 型:左旋if (parent->_bf == 2 && cur->_bf == 1) rotateRR(parent);// LL 型:右旋if (parent->_bf == -2 && cur->_bf == -1) rotateLL(parent);// LR 型:subL 先 左旋, parent 右旋if (parent->_bf == -2 && cur->_bf == 1) {rotateLR(parent);}// RL 型:subR 先 右旋, parent 左旋if (parent->_bf == 2 && cur->_bf == -1) {rotateRL(parent);}break;  // 旋转完了就要 break 出去,否则会继续向上更新,导致 平衡因子出错}else assert(false);
}

4. AVL树的性能(讨论)

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

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

        因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。(这种情况就推荐 我们下一个章节学习的 红黑树 )

5. AVL 树总代码:额外添加其他各种功能函数

额外添加:

Size :求二叉树的节点个数

Height:获取该树的高度

IsBalanceTree:判断本树是否平衡

  •         每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  •         节点的平衡因子是否计算正确

以及 基础函数:拷贝构造、赋值重载、析构函数

#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;/*
1、先手搓一棵二叉搜索树
*/template<class K, class V>
struct AVLTreeNode 
{typedef AVLTreeNode<K, V> Node;pair<K, V> _kv;Node* _left;Node* _right;Node* _parent;int _bf; // 存平衡因子:balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}};template<class K, class V>
class AVLTree
{
public:typedef AVLTreeNode<K, V> Node;AVLTree() = default;~AVLTree() {destory(_root);_root = nullptr;}// 拷贝构造AVLTree(const AVLTree<K, V>& t) {_root = CopyTree(t._root);}// 赋值重载AVLTree<K, V>& operator=(const AVLTree<K, V>& t) {AVLTree tmp(t);std::swap(_root, tmp._root);return *this;}// 查找bool find(const K& key) const {Node* cur = _root;while (cur) {if ((cur->_kv).first < key) {cur = cur->_right;}else if ((cur->_kv).first > key) {cur = cur->_left;}else return true;}return false;}// 插入Node* insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node(kv);return _root;}Node* cur = _root;Node* parent = cur;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;}}// 在 cur 的位置插入该节点cur = new Node(kv);if ((parent->_kv).first > 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++;// 若 bf == 0:说明刚好平衡// 若 bf == 1 or -1:说明刚好打破平衡,但还算做平衡,继续向上更新// 若 bf == 2 or -2:说明失衡,旋转// 其他:其他情况都是不合理的,直接报错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) {// 旋转:同号单旋,异号双旋// RR 型:左旋if (parent->_bf == 2 && cur->_bf == 1) rotateRR(parent);// LL 型:右旋if (parent->_bf == -2 && cur->_bf == -1) rotateLL(parent);// LR 型:subL 先 左旋, parent 右旋if (parent->_bf == -2 && cur->_bf == 1) {rotateLR(parent);}// RL 型:subR 先 右旋, parent 左旋if (parent->_bf == 2 && cur->_bf == -1) {rotateRL(parent);}break;  // 旋转完了就要 break 出去,否则会继续向上更新,导致 平衡因子出错}else assert(false);}return _root;}// RR型:左单旋void rotateRR(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;  // subRL 是有可能为 空的// 2、parent变成subR的左孩子subR->_left = parent;parent->_parent = subR;// 3、subR变成当前子树的根if (parentParent) {if (parent == parentParent->_right)parentParent->_right = subR;else parentParent->_left = subR;subR->_parent = parentParent;}// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空else {_root = subR;subR->_parent = nullptr;}// 单独处理 平衡因子subR->_bf = 0;parent->_bf = 0;}// LL型:右单旋void rotateLL(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;  // subRL 是有可能为 空的 // 2、parent变成subL的右孩子subL->_right = parent;parent->_parent = subL;// 3、subL变成当前子树的根if (parentParent) {if (parent == parentParent->_right)parentParent->_right = subL;else parentParent->_left = subL;subL->_parent = parentParent;}// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空else {_root = subL;subL->_parent = nullptr;}subL->_bf = 0;parent->_bf = 0;}// LR 型:subL 先 左旋, parent 右旋void rotateLR(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;rotateRR(parent->_left);rotateLL(parent);// 双旋后,各个节点平衡因子的更新if (bf == -1) {parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1) {parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == 0) {parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else assert(false);}// RL 型:subR 先 右旋, parent 左旋void rotateRL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;rotateLL(parent->_right);rotateRR(parent);// 双旋后,各个节点平衡因子的更新if (bf == -1) {parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1) {parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 0) {parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else assert(false);}// 删除分三种情况// 1、节点没有孩子// 2、节点只有一个孩子:分是左孩子,还是右孩子// 3、节点有两个孩子// 删除默认删除中序遍历第一个和数值相等的节点Node* erase(const K& key) {if (_root == nullptr) {cout << "整棵树已被删除" << '\n';return _root;}// 看这个元素是否存在if (!find(key)) {cout << "节点不存在" << '\n';return _root;  // 我们这里删除操作失败,也返回 原树}// 查找操作Node* cur = _root;Node* parent = nullptr;while (cur) {if ((cur->_kv).first < key) {parent = cur;cur = cur->_right;}else if ((cur->_kv).first > key) {parent = cur;cur = cur->_left;}else break;}// 删除操作if (cur->_left == nullptr) {if (parent == nullptr) {_root = cur->_right;}else {if ((parent->_kv).first < key) {parent->_right = cur->_right;}else parent->_left = cur->_right;}delete cur;cur = nullptr;}else if (cur->_right == nullptr) {if (parent == nullptr) {_root = cur->_left;}else {if ((parent->_kv).first < key) {parent->_right = cur->_left;}else parent->_left = cur->_left;}delete cur;cur = nullptr;}else {// 取左子树最大值:即左子树的最右边的节点// 取右子树最小值:即右子树的最左边的节点// 我们这里采取第二种方法// 不断向左遍历Node* MinRight = cur->_right;Node* MinRight_parent = cur;while (MinRight->_left) {  // 这里很奇怪MinRight_parent = MinRight;MinRight = MinRight->_left;}if ((MinRight_parent->_kv).first < (MinRight->_kv).first) {MinRight_parent->_right = MinRight->_right;}else MinRight_parent->_left = MinRight->_right;(cur->_kv).first = (MinRight->_kv).first;delete MinRight;}return _root;}// 中序遍历void InOrder() {_InOrder(_root);cout << '\n';}// 获取根节点Node* GetRoot() {return _root;}// 获取该树的高度void Height() {return _Height(_root);}// 本树是否平衡bool IsBalanceTree() {return _IsBalanceTree(_root);}// 获取节点个数int Size() {return _Size(_root);}private:int _Size(Node* pRoot) {if (pRoot == nullptr) return 0;//if (pRoot->_left == nullptr && pRoot->_right == nullptr) return 1;return 1 + _Size(pRoot->_left) + _Size(pRoot->_right);}int _Height(Node* pRoot) {if (pRoot == nullptr)return 0;return 1 + max(_Height(pRoot->_left), _Height(pRoot->_right));}bool _IsBalanceTree(Node* pRoot){// 空树也是AVL树if (nullptr == pRoot) return true;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(pRoot->_left);int rightHeight = _Height(pRoot->_right);int diff = rightHeight - leftHeight;// 这个判断平衡因子的方法 直接 帮我检查出 之前没写 break 导致平衡因子自己更新// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (diff != pRoot->_bf || (diff > 1 || diff < -1))return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(pRoot->_left) && _IsBalanceTree(pRoot->_right);}// 销毁一棵树:后序遍历void destory(Node* root) {if (root == nullptr) {return;}destory(root->_left);destory(root->_right);delete root;}// 拷贝一棵树Node* CopyTree(const Node* root) {if (root == nullptr) {return nullptr;}Node* newRoot = new Node(root->_kv);newRoot->_left = CopyTree(root->_left);newRoot->_right = CopyTree(root->_right);return newRoot;}void _InOrder(const Node* root) {if (root == nullptr) {return;}_InOrder(root->_left);cout << (root->_kv).first  << " : " << (root->_kv).second << '\n';_InOrder(root->_right);}Node* _root = nullptr;
};

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

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

相关文章

【YashanDB知识库】共享集群YAC换IP

【标题】共享集群YAC换IP 【需求分类】安装部署&#xff0c;配置变更 【关键字】安装部署&#xff0c;更换IP&#xff0c;运维&#xff0c;配置变更&#xff0c;高可用&#xff0c;YAC 【需求描述】客户需要将已经部署的YAC集群更换IP&#xff0c;从测试网段切换生产网段 【…

高性能web服务器详解

一、Web服务的基础介绍 正常情况下单次web服务访问的流程简图&#xff1a; 1.1 Web服务介绍 这里介绍的是 Apache 和 NGINX 1.1.1 Apache 经典的Web服务端 Apache 起初由美国的伊利诺伊大学香槟分校的国家超级计算机应用中心开发 目前经历了两大版本分别是 1.X 和 2.X…

高性能web服务器--nginx

下载nginx [rootnginx ~]# wget -c https://nginx.org/download/nginx-1.24.0.tar.gz [rootnginx ~]# tar zxf nginx-1.24.0.tar.gz创建nginx用户 [rootnginx nginx-1.24.0]# useradd -s /sbin/nologin -M nginx先安装依赖 dnf install gcc pcre-devel zlib-devel openssl-d…

p0级别事故 分类

信息化系统 P0至P4事故级别描述了不同严重程度的服务中断或功能故障&#xff0c;其中P0代表最高级别的事故。以下是各级别的详细描述&#xff1a; ● P0&#xff1a;核心业务重要功能不可用&#xff0c;且影响范围广泛&#xff0c;如大面积影响用户。这包括系统崩溃、页面无法访…

Android低内存设备系统优化

切记,所有的优化都遵循一条准则: 空间换时间,时间换空间。 一、前言 我们为什么会觉得卡顿、不流畅? 卡顿等性能问题的最主要根源都是因为渲染性能,Android系统很有可能无法及时完成那些复杂的界面渲染操作。Android系统每隔16ms发出信号,触发对UI进行渲染,如果每次渲染…

springboot整合mybatis以及mybatis-plus 开发

一、springboot整合mybatis 1.注解版 1.1 导入坐标 <dependencies><!--mybatis坐标--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</…

【C++】使用红黑树封装map与set

文章目录 1. 源码分析2. 调整红黑树的结构搭建map、set3. 红黑树的迭代器3.1 普通迭代器3.2 const迭代器3.3 map的operator[ ] 4. 完整代码4.1 RBTree4.2 MyMap4.3 MySet 对于map与set&#xff0c;它们一个是KV模型&#xff0c;一个是K模型&#xff0c;那我们要写两个红黑树吗&…

虚幻5|角色武器装备的数据库学习(不只是用来装备武器,甚至是角色切换也很可能用到)

虚幻5|在连招基础上&#xff0c;给角色添加武器并添加刀光|在攻击的时候添加武器并返回背后&#xff08;第一部分&#xff0c;下一部分讲刀光&#xff09;_unreal 如何给角色添加攻击-CSDN博客 目的&#xff1a;捡起各种不同的武器&#xff0c;捡起的武器跟装备的武器相匹配 …

【Hot100】LeetCode—234. 回文链表

目录 1- 思路快慢指针链表拆分反转链表 2- 实现⭐234. 回文链表——题解思路 3- ACM 实现 原题连接&#xff1a;234. 回文链表 1- 思路 快慢指针链表拆分反转链表 思路 ①将链表拆分前后两个部分——>找拆分点、②反转后面部分、③根据反转结果&#xff0c;同时利用两个指…

MySQL笔记01: MySQL入门_1.3 MySQL启动停止与登录

1.3 MySQL启动停止与登录 1.3.1 MySQL启动与停止 MySQL数据库分为客户端和服务器端&#xff0c;只有服务器端服务开启以后&#xff0c;才可以通过客户端登录MySQL服务端。 首先&#xff0c;以管理员身份运行“命令提示符”&#xff1a; &#xff08;1&#xff09;启动MySQL服务…

python井字棋游戏设计与实现

python实现井字棋游戏 游戏规则&#xff0c;有三个井字棋盘&#xff0c;看谁连成的直线棋盘多谁就获胜 棋盘的展现形式为 棋盘号ABC和位置数字1-9 输入A1 代表在A棋盘1号位数下棋 效果图如下 部分源码如下&#xff1a; 卫星工纵浩 白龙码程序设计&#xff0c;点 代码获取 …

海外短剧平台的局限性与优势:做平台还是选择CPS?

随着国内短剧市场的蓬勃发展&#xff0c;越来越多的目光开始聚焦在海外市场。不少企业和个人都看到了“文化输出”的巨大潜力&#xff0c;希望通过短剧这一形式&#xff0c;吸引海外的观众。然而&#xff0c;在进入海外市场时&#xff0c;我们面临着两种主要的选择&#xff1a;…

STM32 定时器 输入捕获

用于测频率测占空比 IC(Input Capture)输入捕获 输入捕获模式下&#xff0c;当通道输入引脚出现指定电平跳变&#xff08;上升沿/下降沿&#xff09;时&#xff0c;会让当前CNT的值将被锁存到CCR中&#xff0c;可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数…

2024年入职/转行网络安全,该如何规划?_网络安全职业规划

前言 前段时间&#xff0c;知名机构麦可思研究院发布了 《2022年中国本科生就业报告》&#xff0c;其中详细列出近五年的本科绿牌专业&#xff0c;其中&#xff0c;信息安全位列第一。 网络安全前景 对于网络安全的发展与就业前景&#xff0c;想必无需我多言&#xff0c;作为…

制造企业为什么要数字化转型?面临哪些困难?

如何界定制造企业 制造业&#xff08;Manufacturing Industry&#xff09;是指机械工业时代利用某种资源&#xff08;物料、能源、设备、工具、资金、技术、信息和人力等&#xff09;&#xff0c;按照市场要求&#xff0c;通过制造过程&#xff0c;转化为可供人们使用和利用的…

坐牢第二十七天(聊天室)

基于UDP的网络聊天室 一.项目需求&#xff1a; 1.如果有用户登录&#xff0c;其他用户可以收到这个人的登录信息 2.如果有人发送信息&#xff0c;其他用户可以收到这个人的群聊信息 3.如果有人下线&#xff0c;其他用户可以收到这个人的下线信息 4.服务器可以发送系统信息…

8月16日笔记

只有DNS协议出网场景 DNS 协议是一种请求、应答协议&#xff0c;也是一种可用于应用层的隧道技术。DNS 隧道的工作原理很简单&#xff0c;在进行 DNS 查询时&#xff0c;如果查询的域名不在 DNS 服务器本机缓存中&#xff0c;就会访问互联网进行查询&#xff0c;然后返回结果。…

JavaScript基础知识(三)

样式修改 元素.style是对象的一种格式,用于通过设置元素的相关行内样式来设置css,也可以选择相关关联的样式来修改元素相关的样式. 要注意的是,选择相关的样式的时候,样式名是采用小驼峰写法而非是全部小写的方式 类名 添加类名: 元素.classList.add("classname") …

FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer

GSYVideoPlayer是一个国产的移动端视频播放器&#xff0c;它采用了IJKPlayer、Media3(EXOPlayer)、MediaPlayer、AliPlayer等四种播放器内核&#xff0c;支持弹幕、滤镜、广告等多项功能。 GSYVideoPlayer的Github主页为https://github.com/CarGuo/GSYVideoPlayer&#xff0c;截…

『Z-Workshop』 The Graph workshop mini hackathon活动

Community Meetup In Hangzhou ZJUBCA 2024 求是 创新 概述 / OVERVIEW The Graph作为一个去中心化的查询协议&#xff0c;为区块链数据的索引和查询提供了强大的支持。我们希望通过这场黑客松&#xff0c;激发大家对区块链技术更深层次的探索和应用&#xff0c;共同推动这一…