【C++】mapset的底层结构 -- AVL树(高度平衡二叉搜索树)

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

一、AVL树(高度平衡二叉搜索树)

1、概念

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

如果一棵二叉搜索树是高度平衡的,它就是 AVL 树。如果它有 n 个结点,其高度可保持在 O(log₂n),搜索时间复杂度 O(log₂n)。

为什么左右子树高度差不规定成0呢?

因为在 2、4 等节点数的情况下,不可能做到左右高度相等的情况。


2、AVL 树节点的定义

AVL 树节点的定义:
AVL 树节点是一个 三叉链结构,除了 指向左右孩子的指针,还有一个 指向其父亲的指针,数据域是键值对,即 pair 对象,还引入了平衡因子,用来判断是否需要进行平衡操作。
// AVL树节点的定义(KV模型)
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<T>* _left;   // 该节点的左孩子AVLTreeNode<T>* _right;  // 该节点的右孩子AVLTreeNode<T>* _parent; // 该节点的双亲指针pair<K, V> _kv;          // 键值对int _bf;                 // 该节点的平衡因子(balance factor) = 右子树高度-左子树高度// 构造函数AVLTreeNode(const pari<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};// AVL树的定义(KV模型)
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:// 成员函数private:Node* _root;
}

3、AVL树的插入

AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么 AVL 树的插入过程可以分为两步:
  1. 按照二叉搜索树的方式插入新节点到 AVL 树中。
  2. 新节点插入后,AVL 树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了 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) // while循环结束,说明找到适合插入节点的空位置了{if(kv.first > cur->_kv.first) // 插入节点键值k大于当前节点{parent = cur;cur = cur->_right;}else if(kv.first < cur->_kv.first) // 插入节点键值k小于当前节点{ parent = cur;cur = cur->_left;}else // 插入节点键值k等于当前节点{return false;}}// 插入新节点cur = new Node(kv); // 申请新节点// 判断当前节点是父亲的左孩子还是右孩子if (cur->_kv.first > parent->_kv.first){parent->_right = cur;     }else{parent->_left = cur;}cur->_parent = parent;// 控制平衡// 1、更新平衡因子// ...return true;
}

⚪更新平衡因子

(1)插入新节点cur 插入后,parent 的平衡因子一定需要调整,在插入之前,parent 的平衡因子分为三种情况:-1,0,1,分以下两种情况:
  • 如果 cur 插入到 新节点父亲parent) 的左侧,只需给 父亲(parent) 的平衡因子--_bf--即可。
  • 如果 cur 插入到 新节点父亲parent) 的右侧,只需给 父亲(parent) 的平衡因子++(_bf++即可。

 (2)新节点父亲的平衡因子更新以后,又会分为 3 种情况:

此时:parent的平衡因子可能有三种情况:0,正负 1, 正负 2。

  1. 如果更新以后,parent 的平衡因子是 0(则说明插入之前 parent 的平衡因子之前一定为 1/-1),说明父亲所在子树高度没变(因为把矮的那边给填补上了),此时满足 AVL 树的性质,插入成功,不需要继续往上更新
  2. 如果更新以后,parent 的平衡因子是 1/-1(则说明插入之前 parent 的平衡因子 一定为 0),说明父亲所在子树高度增加,需要继续往上更新。(最坏情况:往上一直更新到根节点)。
  3. 如果更新以后,parent 的平衡因子是 2/-2,说明父亲所在子树出现了不平衡,需要对其进行旋转处理
// 插入节点
bool Insert(const pair<K, V>& kv)
{// 控制平衡// 1、更新平衡因子while (parent) // 最坏情况:更新到根节点{// 更新双亲的平衡因子if (cur == parent->_left) // 新节点插入在父亲的左边parent->_bf--;else // 新节点插入在父亲的右边parent->_bf++;// 更新后检测双亲的平衡因子if (0 == pParent->_bf){    break;}//else if (1 == parent->_bf || -1 == parent->_bf)else if (abs(parent->_bf) == 1) // 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉树的高度增加了一层,因此需要继续向上调整{cur = parent;parent = cur->_parent;}else if (abs(parent->_bf) == 2) // 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以parent为根的树进行旋转处理{// 1、父节点的右边高,左边低,需要往左旋if (parent->_bf == 2 && cur->_bf == 1) {RotateL(parent); // 左单旋}// 2、父节点的左边高,右边低,需要往右旋else if ((parent->_bf == -2 && cur->_bf == -1)){RotateR(parent); // 右单旋}// 3、父节点的左边高,且父节点左孩子的右边高else if (parent->_bf == -2 && cur->_bf == 1) {RotateLR(parent); // 左右双旋}// 4、父节点的右边高,且父节点右孩子的左边高else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent); // 右左双旋}break; // 旋转完成,树已平衡,退出循环}// 除了上述3种情况,平衡因子不可能有其它的值,报错处理else{assert(false);}}return true;
}

4、AVL树的旋转

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

旋转的本质:在遵循二叉搜索树的规则下,让左右均衡,降低整棵树的高度。

该进行哪种旋转操作?

引发旋转的路径是直线就是单旋,如果是折线就是双旋

注意:此处看到的树,可能是一颗完整的树,也可能是一颗子树。


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

将新的节点插入到了 parent 左孩子的左子树上,导致的不平衡的情况。

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


【引发右单旋的条件】

  • 父亲左边高,右边低,所以要让父亲往右旋
  • parent 的平衡因子为 -2,parent 左孩子平衡因子为 -1,观察发现,平衡因子都是负数,说明是左边高,也说明了引发旋转的路径是一条直线,所以我们要右旋操作。

【右单旋操作】

1、让 subL 的右子树 subLR 成为 parent 的左子树(因为 subLR 的右子树根节点值 > 30,< 60)。
2、让 parent 成为 subL 的右子树(因为 60 > 30)。
3、让 subL 变成这个子树的根。

这一步操作前需要先判断下:parent 是根节点,还是一个普通子树

  • 如果是根节点,旋转完成后,则更新 subL 为新的根节点。
  • 如果是普通子树(可能是某个节点的左子树,也可能是右子树,这里作一个判断),然后更新 subL 为这个子树的根节点。

4、根据树的结构,更新 parent 和 subL 的平衡因子为 0。


在旋转过程中,更新双亲指针的指向,有以下几种情况需要考虑:

  • 30 节点的右孩子可能存在,也可能不存在。(subL 的右子树 subLR 可能存在,也可能为空。当不为空时才更新 subL 右子树 subLR 的双亲指针指向)。
  • 60 可能是根节点,也可能是子树。(旋转完成后,subL 的双亲节点,可能是空,也可能是 parent 原先的父节点。所以在更新 subL 的双亲指针前需要判断下)。

依次调整 subLR、parent、subL 的位置和双亲指针的指向。 

// 右单旋
void _RotateR(Node* parent)
{  Node* subL = parent->_left; // subL : parent的左孩子Node* subLR = subL->_right; // subLR : parent左孩子的右孩子// 旋转完成之后,让subL的右子树subLR成为parent的左子树parent->_left = subLR;// 如果subLR存在,更新subLR的双亲指针,指向parentif (subLR){subLR->_parent = parent;}// 因为parent可能是棵子树,因此在更新其双亲前必须先保存parent的父节点Node* ppNode = parent->_parent;// 让parent成为subL的右子树subL->_right = parent;// 更新parent的双亲指针,指向subLparent->_parent = subL;// 如果parent是根节点,根新指向根节点的指针if (_root == parent){_root = subL;            // 更新subL为新的根subL->_parent = nullptr; // 更新subL的双亲指针,指向空}// parent不是根节点,就是一个普通子树else{// 判断parent原先是左孩子还是右孩子if (ppNode->_left == parent){ppNode->_left = subL; // parent原先的双亲节点接管subL,subL为这个子树的根}else{ppNode->_right = subL;}subL->_parent = ppNode; // 更新subL的双亲指针}// 根据调整后的结构更新部分节点的平衡因子parent->_bf = pSubL->_bf = 0;
}

(2)新节点插入较高右子树的右侧 —— 右右:左单旋

【引发左单旋的条件】

  • 父亲右边高,左边低,所以要让父亲往左旋
  • parent 的平衡因子为 2,parent 左孩子平衡因子为 1,观察发现,平衡因子都是正数,说明是右边高,也说明了引发旋转的路径是一条直线,所以我们要右旋操作。

【右单旋操作】

1、让 subR 的左子树 subRL 成为 parent 的右子树(因为 subRL 的左子树根节点值 > 30,< 60)。
2、让 parent 成为 subR 的左子树(因为 30 < 60)。
3、让 subR 变成这个子树的根。

这一步操作前需要先判断下:parent 是根节点,还是一个普通子树

  • 如果是根节点,旋转完成后,则更新 subR 为新的根节点。
  • 如果是普通子树(可能是某个节点的左子树,也可能是右子树,这里作一个判断),然后更新 subR 为这个子树的根节点。

4、根据树的结构,更新 parent 和 subR 的平衡因子为 0。


在旋转过程中,更新双亲指针的指向,有以下几种情况需要考虑:

  • subR 的左子树 subRL 可能存在,也可能为空。(当不为空时才更新 subR 左子树 subRL 的双亲指针指向)。
  • 旋转完成后,subR 的双亲节点,可能是空,也可能是 parent 原先的父节点。(所以更新 subR 的双亲指针前需要判断下)。

依次调整 subRL、parent、subR 的位置和双亲指针的指向。

// 左单旋
void treeRotateLeft(Node* parent)
{Node* subR = parent->_right; // subR:父亲的右孩子Node* subRL = subR->_left; // subRL:父亲的右孩子的左孩子(大于父亲,小于subR)// 让subRL成为父亲的右子树parent->_right = subRL;// 如果subRL不为空if (subRL){subRL->_parent = parent; // 更新subRL双亲指针,指向parent}// 因为parent可能是棵子树,因此在更新其双亲前必须先保存parent的父节点Node* ppNode = parent->_parent;// 让parent成为subR的左子树subR->_left = parent; // 更新parent双亲指针的指向parent->_parent = subR;// 判断parent是不是根节点if (parent == _root){_root = subR;            // subR为新的根subR->_parent = nullptr; // subR双亲指针指向空}// 不是根节点,就是一个普通子树else{// 判断parent原先是左孩子还是右孩子if (ppNode->_left == parent){ppNode->_left = subR; // parent原先的双亲节点接管subR,subR为这个子树的根}else{ppNode->_right = subR;}subR->_parent = ppNode; // 更新subR的双亲指针}// 根据树的结构,更新parent和subR的平衡因子parent->_bf = subR->_bf = 0;
}

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

将新的节点插入到了 parent 左孩子的右子树上,导致的不平衡的情况。

这时我们需要的是先对 parent 的右孩子进行一次左旋,再对 parent 进行一次右旋。

将双旋变成单旋后再旋转,即:先对 30 进行左单旋,然后再对 90 进行右单旋,旋转完成后再考虑平衡因子的更新。

旋转之前,60 的平衡因子可能是 -1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整。


【h == 0】 

【引发双旋的条件】

引发旋转的路径是直线就是单旋,如果是折线就是双旋


parent 的平衡因子为 -2,parent 左孩子的平衡因子为 1,观察发现,平衡因子是一负一正,说明左孩子右边高父亲左边高,也说明了引发旋转的路径是一条折线,所以我们要先对左孩子进行左旋操作,再对父亲进行右旋操作


左右双旋操作后,根据树的结构,更新平衡因子时,需要注意:

插入新节点的位置不同,经过左右双旋后,得到树的结构也会有所不同,平衡因子也会有所不同,有以下三种情况:

  1. 新节点插入到了 parent 左孩子的右子树的左边
  2. 新节点插入到了 parent 左孩子的右子树的右边
  3. 新节点就是 parent 左孩子的右孩子

这里可以观察到一个现象,根据这个现象就很好推出旋转后的平衡因子:

节点 60 的左右子树被分走了,左子树最终成为了节点 30 的右子树,右子树最终成了节点 90 的左子树。

void _RotateLR(PNode pParent)
{Node* subL = parent->_left; // 记录parent的左孩子Node* subLR = subL->_right; // 记录parent的左孩子的右孩子// 旋转之前,因为插入新节点的位置不同,subLR的平衡因子可能是-1/0/1int bf = subLR->_bf; // 记录subLR的平衡因子// 先对parent的左孩子进行左单旋RotateL(parent->_left);// 再对parent进行右单旋RotateR(parent);// 旋转完成之后,根据情况对其他节点的平衡因子进行调整subLR->_bf = 0;if (bf == -1){parent->_bf = 1;subL->_bf = 0;}else if (bf == 1){parent->_bf = 0;subL->_bf = -1;}	else if (bf == 0){parent->_bf = 0;subL->_bf = 0;}else{assert(false);}
}

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

将新的节点插入到了 parent 右孩子的左子树上,导致的不平衡的情况。

这时我们需要的是先对 parent 的右孩子进行一次右旋,再对 parent 进行一次左旋。


【h == 1】

【引发双旋的条件】

引发旋转的路径是直线就是单旋,如果是折线就是双旋。
parent 的平衡因子为 2, parent 右孩子平衡因子为 -1,观察发现,平衡因子是一正一负,说明右孩子左边高父亲右边高,也说明了引发旋转的路径是一条折线,所以我们要先对右孩子进行右旋操作,再对父亲进行左旋操作


左右双旋操作后,根据树的结构,更新平衡因子时,需要注意

插入新节点的位置不同,经过右左双旋后,得到树的结构也会有所不同,平衡因子也会有所不同,有以下三种情况:

  • 新节点插入到了 parent 右孩子的左子树的左边
  • 新节点插入到了 parent 右孩子的左子树的右边
  • 新节点就是 parent 右孩子的左孩子

这里可以观察到一个现象,根据这个现象就很好推出旋转后的平衡因子:

节点 60 的左右子树被分走了,左子树 b 最终成了节点 30 的右子树,右子树 c 最终成了节点 90 的左子树。

// 右左双旋
void treeRotateRL(Node* parent)
{Node* subR = parent->_right; // 记录parent的右孩子Node* subRL = subR->_left;   // 记录parent的右孩子的左孩子// 旋转之前,因为插入新节点的位置不同,subRL的平衡因子可能为-1/0/1int bf = subRL->_bf; // 记录subRL的平衡因子RotateR(parent->_right); // 先对parent的右孩子进行右单旋RotateL(parent);         // 再对parent进行左单选// 旋转完成之后,根据树的结构对其他节点的平衡因子进行调整subRL->_bf = 0;if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else if (bf == 1){parent->_bf = -1;subR->_bf = 0;}else if(bf == 0){parent->_bf = 0;subR->_bf = 0;}else{assert(false);}
}
【总结】
假如以 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 为根的子树个高度降低,已经平衡,不需要再向上更新。


5、AVL树的验证

AVL 树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证 AVL 树,可以分两步:
1、验证其为二叉搜索树
  • 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树。

2、验证其为平衡树
  • 每个节点子树高度差的绝对值不超过 1(注意节点中如果没有平衡因子)。
  • 节点的平衡因子是否计算正确。
(1)首先写一个计算当前树高度的函数
// 计算当前树的高度
int Height(Node* root)
{// 当前树为空,则高度为0if (root == nullptr)return 0;// 当前树的高度 = 左右子树中高度最大的那个加1return max(Height(root->_left), Height(root->_right)) + 1;
}

(2)检查AVL树是否平衡:【思路一】自顶向下的暴力解法
bool IsBalance1()
{return _IsBalance(_root);
}bool _IsBalance1(Node* root)
{// 当前树为空,说明是平衡的if (root == nullptr)return true;// 当前树不为空,计算左右子树的高度int leftHT = Height(root->_left);int rightHT = Height(root->_right);int diff = rightHT - leftHT;if (diff != root->_bf) // 检查当前树的平衡因子是否计算正确{cout << root->_kv.first << "平衡因子异常" << endl;return false;}// 左右子树高度相减的绝对值小于2,说明当前树是平衡的,则继续往下判断其它子树return abs(diff) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);
}

(3)检查AVL树是否平衡【思路二】自底向上的高效解法(动态规划,前一个子问题的解,能够用于后一个问题求解)
bool IsBalance2()
{return _IsBalance2(_root) != -1;
}int _IsBalance2(Node* root)
{// 先判断当前树的左、右子树是否平衡,再判断当前树是否平衡// 不平衡返回-1,平衡则返回当前树的高度// 当前树为空,返回高度0if (root == nullptr)return 0;// 当前树不为空,分别计算左右子树的高度int leftHeight = _IsBalance2(root->_left);int rightHeight = _IsBalance2(root->_right);int diff = rightHT - leftHT;if (diff != root->_bf) // 检查当前树的平衡因子是否计算正确{cout << "平衡因子异常:" << root->_kv.first << endl;}// 左子树高度等于-1、右子树高度等于-1、左右子树高度差的绝对值大于1,说明当前树不平衡if (leftHeight == -1 || rightHeight == -1 || abs(diff) > 1)return -1;// 运行到这里来了,说明当前树是平衡的,返回当前树的高度return max(leftHeight, rightHeight) + 1;
}

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

3、验证用例
  • 常规场景 1:{16, 3, 7, 11, 9, 26, 18, 14, 15}
  • 特殊场景 2:{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}

6、AVL树的删除(了解) 

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

7、AVL 树的性能

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

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

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

相关文章

漏洞复现-phpmyadmin_SQL注入 (CVE-2020-5504)

phpmyadmin SQL注入 _&#xff08;CVE-2020-5504&#xff09; 漏洞信息 CVE-2020-5504sql注入漏洞Phpmyadmin 5.00以下 描述 ​ phpMyAdmin是Phpmyadmin团队的一套免费的、基于Web的MySQL数据库管理工具。该工具能够创建和删除数据库&#xff0c;创建、删除、修改数据库表&…

Java学习 5.习题2.

练习题1&#xff1a;判断一个数字是偶数还是奇数 int num110;if(num1%20) {System.out.println("num1是一个偶数");}else{System.out.println("num1是一个奇数");} 练习题2&#xff1a;判断一个数是正数还是负数还是0 int num2-5;if(num2>0) {System.ou…

vantUI(Tabbar标签页)浏览器返回上一页的失效问题

在开发中遇到这样一个问题&#xff0c;由页面1切换到页面2&#xff0c;再点击浏览器的回退&#xff0c;无法回退到页面1。 开始以为是路由配置的有问题&#xff0c;但是子页面可以正常回退&#xff0c;因为replace只是替换路由&#xff0c;而不会往history栈中记录路由&#x…

yarn install 这个命令安装如何加速

yarn install 命令用来安装项目依赖&#xff0c;其速度受多种因素影响&#xff0c;如网络速度、npm/yarn包的源服务器、以及本地缓存等。以下是一些可能帮助你加速 yarn install 的方法&#xff1a; 1. 使用国内镜像 如果你在中国&#xff0c;可以使用淘宝的 npm 镜像&#x…

Android 13.0 系统多个播放器app时,设置默认播放器

1.概述 在13.0的系统产品开发中,对于在系统中有多个播放器的时候,这时候如果调用代码打开播放器,会出现多个播放器列表让用户 选择启动哪个播放器,所以产品开发需求需要设置默认播放器,当打开播放器的时候,就直接打开播放器就可以了,所以就需要 了解查询播放器列表流程,…

【Javascript】json

目录 什么是json&#xff1f; 书写格式 json 序列化和反序列化 序列化 反序列化 什么是json&#xff1f; JSON(JavaScript Object Notation)是⼀种轻量级的数据交换格式&#xff0c;它基于JavaScript的⼀个⼦集&#xff0c;易于⼈的编写和阅读&#xff0c;也易于机器解析…

栈、队列、矩阵的总结

栈的应用 括号匹配 表达式求值&#xff08;中缀&#xff0c;后缀&#xff09; 中缀转后缀&#xff08;机算&#xff09; 中缀机算 后缀机算 总结 特殊矩阵 对称矩阵的压缩存储 三角矩阵 三对角矩阵 稀疏矩阵的压缩存储

OpenCV官方教程中文版 —— 傅里叶变换

OpenCV官方教程中文版 —— 傅里叶变换 前言一、原理二、Numpy 中的傅里叶变换三、OpenCV 中的傅里叶变换四、为什么拉普拉斯算子是高通滤波器&#xff1f; 前言 本小节我们将要学习&#xff1a; • 使用 OpenCV 对图像进行傅里叶变换 • 使用 Numpy 中 FFT&#xff08;快速…

多线程线程池

线程安全问题 当多个线程同时操作同一个共享资源的时候&#xff0c;可能会出现结果不符合预期的问题 解决安全问题 方式一: 同步代码块 作用: 把访问共享资源的核心代码给上锁&#xff0c;以此保证线程安全 格式: synchronized(同步锁) { 访问共享资源的核心代…

202310-MetaPhlAn4安装和使用方法-Anaconda3- centos9 stream

MetaPhlAn 4是一种基于DNA序列的微生物组分析工具&#xff0c;它能够从宏基因组测序数据中识别和分离微生物的组成。以下是安装和使用MetaPhlAn 4的步骤&#xff1a; 安装MetaPhlAn 4&#xff1a; 裸机环境&#xff0c;手动安装&#xff08; 1. 安装依赖项&#xff1a; Met…

unocss和tailwindcss css原子引擎

第一种tailwindcss&#xff1a; tailwindcss官网 https://tailwindcss.com/docs/grid-column 基本介绍及优点分析 Tailwind CSS 中文文档 - 无需离开您的HTML&#xff0c;即可快速建立现代网站 PostCss 处理 Tailwind Css 基本流程 PostCSS - 是一个用 JavaScript 工具和插…

SM3加密udf

SM3加密udf maven xml <dependencies> <!-- 配置日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dep…

67 内网安全-域横向smbwmi明文或hash传递

#知识点1: windows2012以上版本默认关闭wdigest&#xff0c;攻击者无法从内存中获取明文密码windows2012以下版本如安装KB2871997补丁&#xff0c;同样也会导致无法获取明文密码针对以上情况&#xff0c;我们提供了4种方式解决此类问题 1.利用哈希hash传递(pth&#xff0c;ptk等…

读书笔记:c++对话系列,Visitor模式

需要在类体系中加入一个新的虚函数&#xff0c;但不允许改动。 Visitor模式&#xff0c;核心是&#xff1a;将操作作为数据对象传递给类体系预留的Accept函数。 class Personnel{ public:virtual void Pay () 0;virtual void Promote() 0;virtual void Accept(Visitor &…

缓冲channel和限制goroutine并发数

文章目录 3.7.1 用作计数信号量3.7.2 使用缓存channelsync.WaitGroup限制并发数&#xff08;类似上小节&#xff09; 要限制住goroutine的并发, 一定要阻塞住main的goroutine! 一定要阻塞住main的goroutine! 一定要阻塞住main的goroutine! 可以看最后一个例子。 由于带缓冲cha…

尚未解决:use_python()和use_virtualenv()的使用

reticulate包为Python和R之间的互操作性提供了一套全面的工具。该包包含以下功能&#xff1a; 以多种方式从R调用Python&#xff0c;包括RMarkdown、获取Python脚本、导入Python模块以及在R会话中交互使用Python。 R和Python对象之间的转换&#xff08;例如&#xff0c;R和Pan…

2023年中国调音台产业链、产量及市场规模分析[图]

调音台是一种专业音频设备&#xff0c;用于混音、处理和控制音频信号。它通常用于音乐制作、现场演出、录音室以及广播等场景中。调音台允许用户调整不同声音来源的音频信号&#xff0c;使其在混音过程中达到理想的音质和平衡。调音台按信号出来方式可分为&#xff1a;模拟式调…

UVa10976 Fractions Again?!(分数拆分)

1、题目 2、题意 输入正整数 k k k&#xff0c;找到所有正整数 x ≥ y x \ge y x≥y&#xff0c;使得 1 k 1 x 1 y \frac{1}{k} \frac{1}{x} \frac{1}{y} k1​x1​y1​。 3、分析 既然要求找出所有的 x , y x,y x,y&#xff0c;枚举对象自然是 x , y x,y x,y了。可…

LeetCode每日一题——2558. Take Gifts From the Richest Pile

文章目录 一、题目二、题解 一、题目 2558. Take Gifts From the Richest Pile You are given an integer array gifts denoting the number of gifts in various piles. Every second, you do the following: Choose the pile with the maximum number of gifts. If there …

名词解释 MongoDB

MongoDB 是一个面向文档的数据库管理系统&#xff0c;它不使用传统的表格结构&#xff0c;而是将数据组织成类似文档的形式&#xff0c;通常使用JSON格式。 文档数据库&#xff1a;数据以文档的形式存储&#xff0c;每个文档可以包含不同的字段&#xff0c;就像一个文件可以包…