AVL树【C++实现】

文章目录

    • AVL树的概念
    • AVL树节点的定义
    • AVL树的插入
    • AVL树的旋转
        • 新节点插入较高右子树的右侧---右右:左单旋
        • 新节点插入较高左子树的左侧---左左:右单旋
        • 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
        • 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
    • AVL树的验证
    • AVL树的查找
    • AVL树的修改
        • 修改函数一
        • 修改函数二
        • 重载[ ]
    • AVL树的删除
    • AVL树的性能

AVL树的概念

  • AVL树(英语:AVL tree)是计算机科学中最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是 𝑂(log𝑛)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL树得名于它的发明者格奥尔吉·阿杰尔松-韦利斯基和叶夫根尼·兰迪斯,他们在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。

  • 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。

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

  • 它的左右子树都是AVL树

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

  • 平衡因子 = 右子树-左子树

非AVL树:
在这里插入图片描述

  • 在平衡之后:

在这里插入图片描述

在这里插入图片描述

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

AVL树节点的定义

template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parnet;pair<K, V> _kv;int _bf; // balance factorAVLTreeNode(const pair<K, V> kv):_left(nullptr), _right(nullptr), _parnet(nullptr), kv(kv)_bf(0){}
};

AVL树的插入

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

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子
    • 插入到父亲的左边,父亲平衡因子--
    • 插入到父亲的右边,父亲平衡因子++
    • 父亲 bf 平衡因子 == 0,父亲所在子树高度不变,不用继续往上更新,插入结束
    • 父亲 bf 平衡因子 == 1 或者 -1,父亲所在子树高度变了,必须往上更新
    • 父亲 bf 更新后 == 2 或者 -2 ,父亲所在的子树已经不平衡,需要旋转处理
  • 更新后不可能出现其他值,插入之前树是AVL树,平衡因子要么是1,-1,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{return false;}}// 插入节点cur = new Node(kv);if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;cur->_parnet = 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->_parnet;}else if (parent->_bf == -2 || parent->_bf == 2){// 需要旋转// ...break;}else{assert(false);}}return true;
}

AVL树的旋转

如果在一棵原本是平衡的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为根的子树个高度降低,已经平衡,不需要再向上更新。
新节点插入较高右子树的右侧—右右:左单旋

在这里插入图片描述

左单旋的步骤如下:

  1. 让subR的左子树作为parent的右子树。
  2. 让parent作为subR的左子树。
  3. 让subR作为整个子树的根。
  4. 更新平衡因子。

在这里插入图片描述

//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;//1、建立subR和parent之间的关系parent->_parent = subR;subR->_left = parent;//2、建立parent和subRL之间的关系parent->_right = subRL;if (subRL)subRL->_parent = parent;//3、建立parentParent和subR之间的关系if (parentParent == nullptr){_root = subR;subR->_parent = nullptr; //subR的_parent指向需改变}else{if (parent == parentParent->_left){parentParent->_left = subR;}else //parent == parentParent->_right{parentParent->_right = subR;}subR->_parent = parentParent;}//4、更新平衡因子subR->_bf = parent->_bf = 0;
}
新节点插入较高左子树的左侧—左左:右单旋

在这里插入图片描述

  • 上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左
    子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
  1. 30节点的右孩子可能存在,也可能不存在
  2. 60可能是根节点,也可能是子树
  • 如果是根节点,旋转完成后,要更新根节点
  • 如果是子树,可能是某个节点的左子树,也可能是右子树
    在这里插入图片描述

右单旋的步骤如下:

  1. 让subL的右子树作为parent的左子树。
  2. 让parent作为subL的右子树。
  3. 让subL作为整个子树的根。
  4. 更新平衡因子。
// 右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;//1、建立subL和parent之间的关系subL->_right = parent;parent->_parent = subL;//2、建立parent和subLR之间的关系parent->_left = subLR;if (subLR)subLR->_parent = parent;//3、建立parentParent和subL之间的关系if (parentParent == nullptr){_root = subL;_root->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else //parent == parentParent->_right{parentParent->_right = subL;}subL->_parent = parentParent;}//4、更新平衡因子subL->_bf = parent->_bf = 0;
}
新节点插入较高左子树的右侧—左右:先左单旋再右单旋

在这里插入图片描述

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

左右双旋的步骤如下:

  1. 以subL为旋转点进行左单旋。
  2. 以parent为旋转点进行右单旋。
  3. 更新平衡因子。
//左右双旋
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf; //subLR不可能为nullptr,因为subL的平衡因子是1//1、以subL为旋转点进行左单旋RotateL(subL);//2、以parent为旋转点进行右单旋RotateR(parent);//3、更新平衡因子if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false); //在旋转前树的平衡因子就有问题}
}
新节点插入较高右子树的左侧—右左:先右单旋再左单旋

在这里插入图片描述

右左双旋的步骤如下:

  1. 以subR为旋转点进行右单旋。
  2. 以parent为旋转点进行左单旋。
  3. 更新平衡因子。
//右左双旋
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;//1、以subR为轴进行右单旋RotateR(subR);//2、以parent为轴进行左单旋RotateL(parent);//3、更新平衡因子if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == -1){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 1;}else if (bf == 0){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 0;}else{assert(false); //在旋转前树的平衡因子就有问题}
}

AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树如果中序遍历可得到一个有序的序列,就说明为二叉搜索树。
  2. 验证其为平衡树每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)节点的平衡因子是否计算正确。
//判断是否为AVL树
bool IsAVLTree()
{int hight = 0; //输出型参数return _IsBalanced(_root, hight);
}
//检测二叉树是否平衡
bool _IsBalanced(Node* root, int& hight)
{if (root == nullptr) //空树是平衡二叉树{hight = 0; //空树的高度为0return true;}//先判断左子树int leftHight = 0;if (_IsBalanced(root->_left, leftHight) == false)return false;//再判断右子树int rightHight = 0;if (_IsBalanced(root->_right, rightHight) == false)return false;//检查该结点的平衡因子if (rightHight - leftHight != root->_bf){cout << "平衡因子设置异常:" << root->_kv.first << endl;}//把左右子树的高度中的较大值+1作为当前树的高度返回给上一层hight = max(leftHight, rightHight) + 1;return abs(rightHight - leftHight) < 2; //平衡二叉树的条件
}

AVL树的查找

AVL树的查找函数与二叉搜索树的查找方式一模一样,逻辑如下:

  1. 若树为空树,则查找失败,返回nullptr。
  2. 若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
  3. 若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
  4. 若key值等于当前结点的值,则查找成功,返回对应结点。
//查找函数
Node* Find(const K& key)
{Node* cur = _root;while (cur){if (key < cur->_kv.first) //key值小于该结点的值{cur = cur->_left; //在该结点的左子树当中查找}else if (key > cur->_kv.first) //key值大于该结点的值{cur = cur->_right; //在该结点的右子树当中查找}else //找到了目标结点{return cur; //返回该结点}}return nullptr; //查找失败
}

AVL树的修改

修改函数一
//修改函数
bool Modify(const K& key, const V& value)
{Node* ret = Find(key);if (ret == nullptr) //未找到指定key值的结点{return false;}ret->_kv.second = value; //修改结点的valuereturn true;
}
修改函数二
  • 若待插入结点的键值key在map当中不存在,则结点插入成功,并返回插入后结点的指针和true。
  • 若待插入结点的键值key在map当中已经存在,则结点插入失败,并返回map当中键值为key的结点的指针和false。
//插入函数
pair<Node*, bool> Insert(const pair<K, V>& kv)
{if (_root == nullptr) //若AVL树为空树,则插入结点直接作为根结点{_root = new Node(kv);return make_pair(_root, true); //插入成功,返回新插入结点和true}//1、按照二叉搜索树的插入方法,找到待插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first) //待插入结点的key值小于当前结点的key值{//往该结点的左子树走parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first) //待插入结点的key值大于当前结点的key值{//往该结点的右子树走parent = cur;cur = cur->_right;}else //待插入结点的key值等于当前结点的key值{//插入失败(不允许key值冗余)return make_pair(cur, false); //插入失败,返回已经存在的结点和false}}//2、将待插入结点插入到树中cur = new Node(kv); //根据所给值构造一个新结点Node* newnode = cur; //记录新插入的结点if (kv.first < parent->_kv.first) //新结点的key值小于parent的key值{//插入到parent的左边parent->_left = cur;cur->_parent = parent;}else //新结点的key值大于parent的key值{//插入到parent的右边parent->_right = cur;cur->_parent = parent;}//3、更新平衡因子,如果出现不平衡,则需要进行旋转while (cur != _root) //最坏一路更新到根结点{if (cur == parent->_left) //parent的左子树增高{parent->_bf--; //parent的平衡因子--}else if (cur == parent->_right) //parent的右子树增高{parent->_bf++; //parent的平衡因子++}//判断是否更新结束或需要进行旋转if (parent->_bf == 0) //更新结束(新增结点把parent左右子树矮的那一边增高了,此时左右高度一致){break; //parent树的高度没有发生变化,不会影响其父结点及以上结点的平衡因子}else if (parent->_bf == -1 || parent->_bf == 1) //需要继续往上更新平衡因子{//parent树的高度变化,会影响其父结点的平衡因子,需要继续往上更新平衡因子cur = parent;parent = parent->_parent;}else if (parent->_bf == -2 || parent->_bf == 2) //需要进行旋转(此时parent树已经不平衡了){if (parent->_bf == -2){if (cur->_bf == -1){RotateR(parent); //右单旋}else //cur->_bf == 1{RotateLR(parent); //左右双旋}}else //parent->_bf == 2{if (cur->_bf == -1){RotateRL(parent); //右左双旋}else //cur->_bf == 1{RotateL(parent); //左单旋}}break; //旋转后就一定平衡了,无需继续往上更新平衡因子(旋转后树高度变为插入之前了)}else{assert(false); //在插入前树的平衡因子就有问题}}return make_pair(newnode, true); //插入成功,返回新插入结点和true
}
重载[ ]
  1. 调用插入函数插入键值对。
  2. 拿出从插入函数获取到的结点。
  3. 返回该结点value的引用。
  • 如果key不在树中,则先插入键值对<key, V()>,然后返回该键值对中value的引用。
  • 如果key已经在树中,则返回键值为key结点value的引用。
//operator[]重载
V& operator[](const K& key)
{//调用插入函数插入键值对pair<Node*, bool> ret = Insert(make_pair(key, V()));//拿出从插入函数获取到的结点Node* node = ret.first;//返回该结点value的引用return node->_kv.second;
}

AVL树的删除

  • 删除方法和二叉搜索树相同
  1. 先找到待删除的结点。
  2. 若找到的待删除结点的左右子树均不为空,则需要使用替换法进行删除。
//删除函数
bool Erase(const K& key)
{//用于遍历二叉树Node* parent = nullptr;Node* cur = _root;//用于标记实际的删除结点及其父结点Node* delParentPos = nullptr;Node* delPos = nullptr;while (cur){if (key < cur->_kv.first) //所给key值小于当前结点的key值{//往该结点的左子树走parent = cur;cur = cur->_left;}else if (key > cur->_kv.first) //所给key值大于当前结点的key值{//往该结点的右子树走parent = cur;cur = cur->_right;}else //找到了待删除结点{if (cur->_left == nullptr) //待删除结点的左子树为空{if (cur == _root) //待删除结点是根结点{_root = _root->_right; //让根结点的右子树作为新的根结点if (_root)_root->_parent = nullptr;delete cur; //删除原根结点return true; //根结点无祖先结点,无需进行平衡因子的更新操作}else{delParentPos = parent; //标记实际删除结点的父结点delPos = cur; //标记实际删除的结点}break; //删除结点有祖先结点,需更新平衡因子}else if (cur->_right == nullptr) //待删除结点的右子树为空{if (cur == _root) //待删除结点是根结点{_root = _root->_left; //让根结点的左子树作为新的根结点if (_root)_root->_parent = nullptr;delete cur; //删除原根结点return true; //根结点无祖先结点,无需进行平衡因子的更新操作}else{delParentPos = parent; //标记实际删除结点的父结点delPos = cur; //标记实际删除的结点}break; //删除结点有祖先结点,需更新平衡因子}else //待删除结点的左右子树均不为空{//替换法删除//寻找待删除结点右子树当中key值最小的结点作为实际删除结点Node* minParent = cur;Node* minRight = cur->_right;while (minRight->_left){minParent = minRight;minRight = minRight->_left;}cur->_kv.first = minRight->_kv.first; //将待删除结点的key改为minRight的keycur->_kv.second = minRight->_kv.second; //将待删除结点的value改为minRight的valuedelParentPos = minParent; //标记实际删除结点的父结点delPos = minRight; //标记实际删除的结点break; //删除结点有祖先结点,需更新平衡因子}}}if (delParentPos == nullptr) //delParentPos没有被修改过,说明没有找到待删除结点{return false;}//记录待删除结点及其父结点(用于后续实际删除)Node* del = delPos;Node* delP = delParentPos;//更新平衡因子while (delPos != _root) //最坏一路更新到根结点{if (delPos == delParentPos->_left) //delParentPos的左子树高度降低{delParentPos->_bf++; //delParentPos的平衡因子++}else if (delPos == delParentPos->_right) //delParentPos的右子树高度降低{delParentPos->_bf--; //delParentPos的平衡因子--}//判断是否更新结束或需要进行旋转if (delParentPos->_bf == 0)//需要继续往上更新平衡因子{//delParentPos树的高度变化,会影响其父结点的平衡因子,需要继续往上更新平衡因子delPos = delParentPos;delParentPos = delParentPos->_parent;}else if (delParentPos->_bf == -1 || delParentPos->_bf == 1) //更新结束{break; //delParent树的高度没有发生变化,不会影响其父结点及以上结点的平衡因子}else if (delParentPos->_bf == -2 || delParentPos->_bf == 2) //需要进行旋转(此时delParentPos树已经不平衡了){if (delParentPos->_bf == -2){if (delParentPos->_left->_bf == -1){Node* tmp = delParentPos->_left; //记录delParentPos右旋转后新的根结点RotateR(delParentPos); //右单旋delParentPos = tmp; //更新根结点}else if(delParentPos->_left->_bf == 1){Node* tmp = delParentPos->_left->_right; //记录delParentPos左右旋转后新的根结点RotateLR(delParentPos); //左右双旋delParentPos = tmp; //更新根结点}else //delParentPos->_left->_bf == 0{Node* tmp = delParentPos->_left; //记录delParentPos右旋转后新的根结点RotateR(delParentPos); //右单旋delParentPos = tmp; //更新根结点//平衡因子调整delParentPos->_bf = 1;delParentPos->_right->_bf = -1;break; //更正}}else //delParentPos->_bf == 2{if (delParentPos->_right->_bf == -1){Node* tmp = delParentPos->_right->_left; //记录delParentPos右左旋转后新的根结点RotateRL(delParentPos); //右左双旋delParentPos = tmp; //更新根结点}else if(delParentPos->_right->_bf == 1){Node* tmp = delParentPos->_right; //记录delParentPos左旋转后新的根结点RotateL(delParentPos); //左单旋delParentPos = tmp; //更新根结点}else //delParentPos->_right->_bf == 0{Node* tmp = delParentPos->_right; //记录delParentPos左旋转后新的根结点RotateL(delParentPos); //左单旋delParentPos = tmp; //更新根结点//平衡因子调整delParentPos->_bf = -1;delParentPos->_left->_bf = 1;break; //更正}}//delParentPos树的高度变化,会影响其父结点的平衡因子,需要继续往上更新平衡因子delPos = delParentPos;delParentPos = delParentPos->_parent;//break; //error}else{assert(false); //在删除前树的平衡因子就有问题}}//进行实际删除if (del->_left == nullptr) //实际删除结点的左子树为空{if (del == delP->_left) //实际删除结点是其父结点的左孩子{delP->_left = del->_right;if (del->_right)del->_right->_parent = delP;}else //实际删除结点是其父结点的右孩子{delP->_right = del->_right;if (del->_right)del->_right->_parent = delP;}}else //实际删除结点的右子树为空{if (del == delP->_left) //实际删除结点是其父结点的左孩子{delP->_left = del->_left;if (del->_left)del->_left->_parent = delP;}else //实际删除结点是其父结点的右孩子{delP->_right = del->_left;if (del->_left)del->_left->_parent = delP;}}delete del; //实际删除结点return true;
}

AVL树的性能

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

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

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

相关文章

【C++课程学习】:C++入门(函数重载)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f308;函数重载&#xff1a; &#x1f349;1.参数个数不同&#xff1a; &#x1f349;2.参数…

ceph对象储存的使用

radosgw-admin user create --uid“user1” --display-name“user1” #创建用户 sudo apt install s3cmd cephadminceph-mgr01:~/ceph-cluster/s3$ s3cmd --configure Enter new values or accept defaults in brackets with Enter. Refer to user manual for detailed desc…

Visual C++ Redistributable下载

安装程序的时候提示丢失mfc140u.dll 如下图,查了资料说可以下载Visual C Redistributable来进行处理 下载Visual C Redistributable 1.打开网站 https://www.microsoft.com/zh-cn/download/details.aspx?id48145&751be11f-ede8-5a0c-058c-2ee190a24fa6True) 2.点击下载 …

Java面试题:Redis持久化问题

Redis持久化问题 RDB (Redis Database Backup File) Redis数据快照 将内存中的所有数据都记录到磁盘中做快照 当Redis实例故障重启时,从磁盘读取快照文件恢复数据 使用 save/bgsave命令进行手动快照 save使用主进程执行RDB,对所有命令都进行阻塞 bgsave使用子进程执行R…

创新案例 | AI数据驱动下的全域数字化转型的五大关键洞见

近年来通过全域数字化转型在竞争激烈的市场中脱颖而出。传统零食行业面临市场竞争加剧和消费者需求多样化的挑战&#xff0c;如何利用数据驱动和AI技术&#xff0c;能更好地实现会员运营效率和用户满意度的显著提升呢&#xff1f;本文将探讨全域数字化转型的五大关键洞见&#…

【C++】STL中List的基本功能的模拟实现

前言&#xff1a;在前面学习了STL中list的使用方法&#xff0c;现在我们就进一步的讲解List的一些基本功能的模拟实现&#xff0c;这一讲博主认为是最近比较难的一个地方&#xff0c;各位一起加油。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; …

Excel 交叉表的格转成列,行转成格

Excel里交叉表的左表头是卡车号&#xff0c;上表头是工作&#xff0c;交叉格是工作编号。 ABCD1Truck NumberJob1Job2Job3271592859285928372395859282971473297159282971 要求&#xff1a;将交叉格转为列&#xff0c;左表头转为格。 ABC1297139585928272727137371473715726…

深度学习的实用性探究:虚幻还是现实?

深度学习的实用性探究&#xff1a;虚幻还是现实&#xff1f; 深度学习作为人工智能领域的一个热点&#xff0c;已经在学术和工业界引起了广泛的关注。尽管深度学习技术显示出惊人的性能和潜力&#xff0c;但有时它们给人的感觉是“虚”的&#xff0c;或许是因为它们的抽象性和…

微服务框架下,因发送端与消费端的vhost不一致,导致rabbitmq出现严重的消息堆积

一、背景 在生产环境下&#xff0c;rabbitmq机器出现磁盘空间不足的报警&#xff0c;发现是某个队列的消息只有生产&#xff0c;迟迟没有消费。 可以得到的信息是&#xff1a; 队列queue是data_center_file_change_queue队列绑定的交换机是resourceChangeExchange&#xff0c…

PLC通过Profinet转Modbus网关与流量计通讯案例

1、案例背景 在工业自动化系统中&#xff0c;PLC(可编程逻辑控制器)与流量计之间的通信是保证以后设备生产数据准确传输和实现控制功能的关键。但是&#xff0c;由于PLC和流量计可能使用不同的通信协议(如Profinet和Modbus)&#xff0c;因此需要一种转换机制来实现它们之间的通…

uniapp uni-popup内容被隐藏问题

今天开发新需求的时候发现uni-popup 过一会就被隐藏掉只留下遮罩(css被更改了)&#xff0c;作者进行了如下调试。 1.讲uni-popup放入其他节点内 失败&#xff01; 2.在生成dom后在打开 失败&#xff01; 3.uni-popup将该节点在包裹一层 然后将统计设置样式&#xff0c;v-if v-s…

算法题--华为od机试考试(围棋的气、用连续自然数之和来表达整数、亲子游戏)

目录 围棋的气 题目描述 输入描述 示例1 输入 输出 解析 答案 用连续自然数之和来表达整数 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入 输出 解析 答案 亲子游戏 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入…

开发人员必备的常用工具合集-lombok

Project Lombok 是一个 java 库&#xff0c;它会自动插入您的编辑器和构建工具&#xff0c;为您的 Java 增添趣味。 再也不用编写另一个 getter 或 equals 方法了&#xff0c;只需一个注释&#xff0c;您的类就拥有了一个功能齐全的构建器&#xff0c;自动化了您的日志记录变量…

低代码:加速企业数字化转型的利器

随着企业数字化转型步伐的加快&#xff0c;低代码开发平台迅速成为市场的焦点。凭借其能简化开发流程、缩短交付时间和降低成本等优势&#xff0c;低代码已经赢得了企业和开发人员的广泛认可&#xff0c;已成为推动企业数字化转型、提高企业创新效率、竞争力的关键工具。本文将…

红酒:如何选择适合的红酒储存容器

选择适合的红酒储存容器对于保持雷盛红酒的品质和风味至关重要。不同的容器具有不同的优缺点&#xff0c;因此应根据个人需求和条件进行选择。以下是一些常见的红酒储存容器的特点和适用场景&#xff1a; 玻璃瓶&#xff1a;玻璃瓶是常见的红酒储存容器。它具有良好的密封性能、…

粘性代理 vs 轮换代理: 特点、优势与选择指南

在网络领域&#xff0c;代理服务器是一种常见的工具&#xff0c;用于隐藏真实IP地址并提供更安全和匿名的网络体验。 粘性代理和轮换代理是两种常见的代理类型&#xff0c;它们在IP持久性和变更频率等方面有所不同。 本文将介绍粘性代理和轮换代理的区别&#xff0c;并分析在…

wordpress里面嵌入哔哩哔哩视频的方法

我们正常如果从blibli获取视频分享链接然后在wordpress里面视频URL插入&#xff0c;发现是播放不了的 而视频嵌入代码直接粘贴呢窗口又非常的小 非常的难受&#xff0c;就需要更改一下代码。你可以在在allowfullscreen"true"的后面&#xff0c;留1个空格&#xff…

GWT 与 Python App Engine 集成

将 Google Web Toolkit (GWT) 与 Python App Engine 集成可以实现强大的 Web 应用程序开发。这种集成允许你使用 GWT 的 Java 客户端技术构建丰富的用户界面&#xff0c;并将其与 Python 后端结合在一起&#xff0c;后端可以运行在 Google App Engine 上。 1、问题背景 在 Pyt…

从零开始手把手Vue3+TypeScript+ElementPlus管理后台项目实战四(引入Axios,并调用第一个接口)

RealWorld接口综述 本项目调用的是RealWorld项目的开放接口。 接口文档如下&#xff1a; https://main--realworld-docs.netlify.app/docs/specs/backend-specs/endpoints https://main--realworld-docs.netlify.app/docs/specs/frontend-specs/swagger RealWorld 是一个适…