C++:高度平衡二叉搜索树(AVLTree) [数据结构]
目录
一、AVL树
二、AVL树的理解
1.AVL树节点的定义
2.AVL树的插入
2.1更新平衡因子
3.AVL树的旋转
三、AVL的检查
四、完整代码实现
一、AVL树
AVL树是什么?我们对 map / multimap / set / multiset 进行了简单的介绍,可以发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的。但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成 O(N),因此 map、set 等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

此二叉树插入元素有序,进行查找的时候效率会相当低下,甚至当接近为单支树的时候,查找效率会相当于在顺序表中的查找效率。因此俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis发明了一个解决方案:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。这也就是AVL树的由来。
一棵
AVL树或者是空树,或者是具有以下性质的二叉搜索树:1、它的左右子树都是AVL树
2、左右子树高度之差的绝对值不会超过1
左右子树的高度差也叫做平衡因子,在这样的树中进行搜索,时间复杂度是
O(logN)。
二、AVL树的理解
1.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;
}
2.AVL树的插入
AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么 AVL 树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点到 AVL 树中。
- 新节点插入后,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;
}
2.1更新平衡因子
(1) 插入新节点cur 插入后,AVL树的平衡性可能会遭到破坏,引入平衡因子的意义也就在于此,parent 的平衡因子一定需要调整,在插入之前,parent 的平衡因子分为三种情况:-1,0,1,分以下两种情况:
①如果插入到 新节点父亲(parent)的左侧,只需给父亲(parent)的平衡因子--(
_bf--)即可。②如果插入到 新节点父亲(parent)的右侧,只需给父亲(parent)的平衡因子++(_bf++)即可。
(2) 新节点父亲的平衡因子更新以后,又会分为 3 种情况:
①如果更新以后,parent 的平衡因子是 0(则说明插入之前 parent 的平衡因子之前一定为 1/-1),说明父亲所在子树高度没变(因为把矮的那边给填补上了),此时满足 AVL 树的性质,插入成功,不需要继续往上更新。
②如果更新以后,parent 的平衡因子是 1/-1(则说明插入之前 parent 的平衡因子 一定为 0),说明父亲所在子树高度增加,需要继续往上更新。(最坏情况:往上一直更新到根节点)。
③如果更新以后,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;
}
3.AVL树的旋转
那当上面的情况已经发生,AVL树应该如何进行平衡?这就引入了旋转的概念,旋转是在遵循二叉搜索树的规则下,让左右均衡,降低整颗树的高度。
在AVL树中,当破坏了AVL树的平衡后,总共有四种会引发的旋转:
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. 新节点插入在较高右子树的右侧,引发左单旋

[操作]
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 进行一次左旋。

左右双旋操作后,根据树的结构,更新平衡因子时,需要注意:
插入新节点的位置不同,经过右左双旋后,得到树的结构也会有所不同,平衡因子也会有所不同,有以下三种情况:
- 新节点插入到了 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);}
}
4. 新节点插入较高左子树的右侧,引发先左单旋再右单旋
将新的节点插入到了 parent 左孩子的右子树上,导致的不平衡的情况。这时我们需要的是先对 parent 的右孩子进行一次左旋,再对 parent 进行一次右旋。
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);}
}
【总结】
假如以 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 为根的子树个高度降低,已经平衡,不需要再向上更新。
三、AVL的检查
依据上面的实现可以基本实现AVL树,那如何验证AVL树是否正确?其实验证也很简单,只需要看每个节点的平衡因子是否等于对应的右子树减左子树的值即可。
// 计算当前树的高度
int Height(Node* root)
{// 当前树为空,则高度为0if (root == nullptr)return 0;// 当前树的高度 = 左右子树中高度最大的那个加1return max(Height(root->_left), Height(root->_right)) + 1;
}// 保持树的封装 进行检查AVL树
bool IsBalance1()
{return _IsBalance(_root);
}// 进行检查AVL树
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);
}
四、完整代码实现
#pragma oncetemplate<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;//三叉链AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;//平衡因子pair<K, V> _kv;//key//构造函数AVLTreeNode(const pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){//1.先按二叉搜索树的规则插入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->_left;}else if (cur->_kv.first < kv.first){paretn = cur;cut = cur->_right;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//2.更新平衡因子while (parent)//有可能要更新到根节点{if (cur == parent->_right){parent->_bf ++;}else{parent->_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){//parent 的子树出现不平衡了,需要旋转处理}}return true;}//左单旋void RotateL(Node* parent){/*Node* subR = parent->_right;parent->_right = subR->_left;subR->_left = parent;*///没有处理每个节点的parent,Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;//1.原来的parent是这棵树的根,现在subR是树的根//2.parent不是整棵树的根,那么链接关系要变,subR就要顶替parent的位置if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}parent->_bf = subR->_bf = 0;}//右单旋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 (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)PPNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}subL->_bf = parent->_bf = 0;}// 先右单旋再左单旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);if (bf == 0){// 自己就是新增的节点parent->_bf = subR->_bf = 0;}else if (bf == -1){// 在左子树进行的插入parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1){// 在右子树进行的插入subRL->_bf = 0;subR->_bf = 0;parent->_bf = -1;}else{assert(false);}}// 先进行左单旋,再进行右单旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);// 更换平衡因子if (bf == 0){subL->_bf = subLR->_bf = 0;}else if (bf == 1){// 插入在右子树subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){// 插入在左子树parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}// 用于检查树的高度int TreeHeight(Node* root){if (root == nullptr)return 0;int leftheight = TreeHeight(root->_left);int rightheight = TreeHeight(root->_right);return max(leftheight, rightheight) + 1;}// 保持树的封装 进行检查AVL树bool IsBalance(){return _IsBalance(root);}// 进行检查AVL树bool _IsBalance(Node* root){if (root == nullptr)return true;int leftheight = TreeHeight(root->_left);int rightheight = TreeHeight(root->_right);if (rightheight - leftheight != root->_bf)return false;return abs(rightheight - leftheight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);}private:Node* _root = nullptr;
};
【总结】
AVLTree 高度平衡二叉搜索树。
1.搜索树
2.要求树的左右子树的高度差不超过1,树的左子树和右子树也满足高度差不超过1,(树及所有子树都要满足前面的要求)
3.为了方便实现,引入了平衡因子(这个只是一种方式,并不是必须要这种)
4.平衡因子 = 右子树的高度 - 左子树的高度
5.高度基本可以控制在O(logN)
6.AVLTree增删查改的效率就是O(logN)
7.平衡因子到底是用来干嘛的?
8.AVLTree 的插入三步:
- 按二叉搜索树方式插入
- 更新平衡因子
- 如果更新完,没有违反规定,则插入结束,有违规,则旋转处理
9.不是所有的平衡因子都会被影响,被影响的是祖先,沿着路径往根节点更新祖先节点的平衡因子,但也不是所有的祖先都会被影响。
1.cur 是 parent 的左,parent->bf--,cur 是 parent 的右, parent->bf++,
2.更新完parent的bf 后,如果parent->bf == 0 ,说明parent的高度不变,更新结束,插入完成
解释: 说明更新前,parent 的 bf 是 -1 or 1, 现在变成0, 说明把矮的那边子树给填上了,说明高度没有变,对上层没有影响
3.更新完parent 的 bf 后, 如果parent->bf == 1 or -1 , 说明parent 的高度变了,继续往上更新
解释: 说明跟新前,parent 的 bf 是 0 , 现在变成了 1 or -1, 说明 一边高,对上层有影响,继续往上更新。
4.再次更新完,parent 的 bf === -2 or 2 说明parent所在的子树出现了不平衡,需要旋转处理
5.旋转处理:
- 旋转完后,还得是搜索树
- 旋转之后能把它变成平衡(左子树,右子树高度差不超过1)(左单旋,右单旋)
相关文章:
C++:高度平衡二叉搜索树(AVLTree) [数据结构]
目录 一、AVL树 二、AVL树的理解 1.AVL树节点的定义 2.AVL树的插入 2.1更新平衡因子 3.AVL树的旋转 三、AVL的检查 四、完整代码实现 一、AVL树 AVL树是什么?我们对 map / multimap / set / multiset 进行了简单的介绍,可以发现,这几…...
建筑兔零基础自学python记录18|实战人脸识别项目——视频检测07
本次要学视频检测,我们先回顾一下图片的人脸检测建筑兔零基础自学python记录16|实战人脸识别项目——人脸检测05-CSDN博客 我们先把上文中代码复制出来,保留红框的部分。 然后我们来看一下源代码: import cv2 as cvdef face_detect_demo(…...
【MySQL数据库】Ubuntu下的mysql
目录 1,安装mysql数据库 2,mysql默认安装路径 3,my.cnf配置文件? 4,mysql运用的相关指令及说明 5,数据库、表的备份和恢复 mysql是一套给我们提供数据存取的,更加有利于管理数据的服务的网络程序。下…...
[MySQL#1] database概述 常见的操作指令 MySQL架构 存储引擎
#1024程序员节|征文# 目录 一. 数据库概念 0.连接服务器 1. 什么是数据库 口语中的数据库 为什么数据不直接以文件形式存储,而需要使用数据库呢? 总结 二. ??基础操作 三. 主流数据库 四. 基础知识 服务器,数据库&…...
1.从零开始学会Vue--{{基础指令}}
全新专栏带你快速掌握Vue2Vue3 1.插值表达式{{}} 插值表达式是一种Vue的模板语法 我们可以用插值表达式渲染出Vue提供的数据 1.作用:利用表达式进行插值,渲染到页面中 表达式:是可以被求值的代码,JS引擎会将其计算出一个结果 …...
VS2022中.Net Api + Vue 从创建到发布到IIS
VS2022中.Net Api Vue 从创建到发布到IIS 前言一、先决条件二、创建项目三、运行项目四、增加API五、发布到IIS六、设置Vue的发布 前言 最近从VS2019 升级到了VS2022,终于可以使用官方的.Net Vue 组合了,但是使用过程中还是有很多问题,这里记录一下. 一、先决条件 Visual …...
RFID技术在制造环节的应用与价值
在现代制造业中,信息化和智能化已经成为企业提升竞争力的重要手段。RFID技术因其非接触式、远距离和高效识别的特点,广泛应用于生产的多个环节。本文将详细解读生产过程中RFID的关键应用场景,并结合实际案例,展示其为制造业带来的…...
(前端基础)HTML(一)
前提 W3C:World Wide Web Consortium(万维网联盟) Web技术领域最权威和具有影响力的国际中立性技术标准机构 其中标准包括:机构化标准语言(HTML、XML) 表现标准语言(CSS) 行为标准…...
Linux文件管理:硬链接与软链接
文章目录 1. 硬链接的设计目的(1)节省存储空间(2)提高文件管理效率(3)数据持久性(4)文件系统的自然特性 2. 软链接的设计目的**(1)跨文件系统引用****&#x…...
pnpm, eslint, vue-router4, element-plus, pinia
利用 pnpm 创建 vue3 项目 pnpm 包管理器 - 创建项目 Eslint 配置代码风格(Eslint用于规范纠错,prettier用于美观) 在 设置 中配置保存时自动修复 提交前做代码检查 husky是一个 git hooks工具(git的钩子工具,可以在特定实际执行特…...
在软件产品从开发到上线过程中,不同阶段可能出现哪些问题,导致软件最终出现线上bug
在软件产品从开发到上线的全生命周期中,不同阶段都可能因流程漏洞、技术疏忽或人为因素导致线上问题。以下是各阶段常见问题及典型案例: 1. 需求分析与设计阶段 问题根源:业务逻辑不清晰或设计缺陷 典型问题: 需求文档模糊&#…...
Spring Boot中如何自定义Starter
文章目录 Spring Boot中如何自定义Starter概念和作用1. 概念介绍2. 作用和优势2.1 简化依赖管理2.2 提供开箱即用的自动配置2.3 标准化和模块化开发2.4 提高开发效率2.5 提供灵活的配置覆盖3. 应用场景创建核心依赖1. 确定核心依赖的作用2. 创建 starter-core 模块2.1 依赖管理…...
制作Ubuntu根文件
系列文章目录 Linux内核学习 Linux 知识(1) Linux 知识(2) WSL Ubuntu QEMU 虚拟机 Linux 调试视频 PCIe 与 USB 的补充知识 vscode 使用说明 树莓派 4B 指南 设备驱动畅想 Linux内核子系统 Linux 文件系统挂载 QEMU 通过网络实现…...
SpringBoot快速接入OpenAI大模型(JDK8)
使用AI4J快速接入OpenAI大模型 本博文给大家介绍一下如何使用AI4J快速接入OpenAI大模型,并且如何实现流式与非流式的输出,以及对函数调用的使用。 介绍 由于SpringAI需要使用JDK17和Spring Boot3,但是目前很多应用依旧使用的JDK8版本&…...
UniApp 中制作一个横向滚动工具栏
前言 最近在用 UniApp 开发项目时,需要一个横向滑动的工具栏。常见的工具栏一般都是竖着的,但横向滑动的工具栏不仅能展示更多内容,还能让界面看起来更加丰富。不过很多朋友可能会发现,如何让内容“横着”展示又不变形、能流畅滚…...
react中如何获取真实的dom
在 React 中,获取真实的 DOM 元素通常通过 ref 来实现。ref 是一个特殊的属性,用于引用组件或 DOM 元素的实例。你可以通过 ref 获取到组件的真实 DOM 元素或组件实例。 1. 函数组件中的 useRef 在函数组件中,获取 DOM 元素的引用需要使用 …...
5G与物联网的协同发展:打造智能城市的未来
引言 随着科技的不断进步,智能城市的概念已经不再是科幻小说中的幻想,它正在逐步走进我们的生活。而这背后的两大驱动力无疑是 5G和 物联网(IoT)。5G网络以其高速率、低延迟、大容量的优势,与物联网的强大连接能力相结…...
【Qt】实现定期清理程序日志
在现有Qt程序中实现可配置日志保存天数的代码示例,分为界面修改、配置存储和核心逻辑三部分: // 1. 在配置文件(如settings.h)中添加保存天数的配置项 class Settings { public:int logRetentionDays() const {return m_settings…...
git bisect 使用二分法查找引入错误的提交
git bisect 使用二分法查找引入错误的提交 Git bisect 命令官方文档 git bisect 这个命令使用二分搜索算法来查找项目历史中哪个提交引入了一个错误 使用该命令时,首先告诉它一个已知包含错误的 “坏” 提交 以及一个已知在错误出现之前的 “好” 提交 然后 git b…...
一种面向车载时间敏感网络的联合路由与时隙调度负载均衡算法
论文标题 中文标题:一种面向车载时间敏感网络的联合路由与时隙调度负载均衡算法 英文标题:A Joint Routing and Time-Slot Scheduling Load Balancing Algorithm for In-Vehicle TSN 作者信息 Bo Xu, Xinrui Chang, Dongyang Xu, Shuo Wang, Uzair As…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
