【C++从0到王者】第三十三站:AVL树
文章目录
- 前言
- 一、AVL 树的概念
- 二、AVL树的实现
- 1. AVL树的结点定义
- 2. AVL树的插入之插入部分
- 3. AVL树的插入之平衡因子的改变
- 4. AVL树的插入之左旋
- 5. AVL树的左旋抽象图
- 6.AVL树的右旋抽象图
- 7. AVL树的双旋
- 8. AVL树的右左双旋
- 9. AVL树的右左双旋的本质
- 10. AVL树的左右双旋
- 11. AVL树的验证
- 12. AVL树的删除
- 三、AVL树的性能
前言
我们常见的查找方式有以下几种:
-
暴力查找(效率太低)
-
二分查找(条件太苛刻了,有序,伴随着插入和删除数据效率太低,维护成本很高)
-
二叉搜索树(极端场景下退化为了链表,效率太低)
以上是我们已经知道的几种查找方式,上面的方式或多或少都存在许多问题。于是,我们便有了下面几种查找方式
- 二叉平衡搜索树(AVL树/红黑树)
- 多叉平衡搜索树(B树系列)
- 哈希表
一、AVL 树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1) (右子树的高度减去左子树的高度)
AVL树其实就是高度平衡二叉搜索树,**这里的平衡不是相等,而是高度差不超过。**其实是因为无法做到高度相等,只能做到高度差不超过1。因为总有一些结点是无法做到满二叉树的
如下是一颗典型的AVL树
对于这种AVL树,它的增删查改效率都是很高的。都是logN级别的复杂度
试想一下:
如果是满二叉树,当它高度为h的时候,他的总结点个数为2^h-1==N。
如果是平衡二叉树的话,当它的高度为h的时候,它的节点个数为2h-X==N。这里的X属于[1,2(h-1)-1]
所以它的效率为logN
二、AVL树的实现
1. AVL树的结点定义
如下所示,我们将AVL结点的值定义为一个pair对象,目的是使用key-value模型。bf是一个平衡因子。这里我们还需要使用三叉链结构
template<class K, class V>
struct AVLTreeNode
{pair<K,V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}};
2. AVL树的插入之插入部分
关于AVL树的插入,我们根据它的特性,不难得知,首先要先将一个值给插入进去,再去考虑控制平衡。
bool Insert(const pair<K, V>& kv){Node* newnode = new Node(kv);if (_root == nullptr){_root = newnode;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first > newnode->_kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < newnode->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}if (parent->_kv.first > newnode->_kv.first){parent->_left = newnode;}else{parent->_right = newnode;}newnode->_parent = parent;//平衡return true;}
如上代码所示,与二叉搜索树的方法类似,我们先想办法插入一个值进去。上面我们已经成功插入了一个数据。这一点是不难的。
-
我们先申请一个结点
-
如果根节点为空,那么我们直接将这个结点交给根节点即可。
-
如果根节点不为空,那么由于是二叉搜索树,那么我们先设定两个结点指针,一个指向空称作parent,一个指向根节点称作cur。
-
然后我们让cur去与我们要插入结点进行比较,持续迭代,最终我们就可以找出cur为待插入结点的位置,parent为要插入结点的父亲
-
然后我们根据待插入的值与父节点进行比较,从而确定插入左子树还是右子树。这一步是非常有必要的。
在第五步中,我们极其容易犯一个错误,就是我们想当然的认为,cur此时就是要插入的孩子结点,所以我们会写出cur = newnode这样奇葩的代码,其实是万万不可的。我们可以画个内存图了解一下
- 插入号了结点以后,我们就可以开始将链接关系了。
3. AVL树的插入之平衡因子的改变
将结点插入以后,我们需要做的就是控制平衡。因为我们AVL树的最终目的还是控制平衡。
我们先来看第一种情况
我们就以这颗树作为例子
首先它本身已经是一颗AVL树了,我们先考虑对右子树的插入
有如下几种情况
当我们插入之后,需要做的就是改变平衡因子。我们先来分析如何改变。
根据上图,我们可以自己观测到,它的改变如下所示
对于这些平衡因子的改变,我们可以总结如下的规律
- 新增在左,parent平衡因子减减
- 新增在右,parent平衡因子加加
- 更新后parent平衡因子==0,说明parent所在的子树高度不变,不再影响祖先,不再沿着祖先的路径往上更新
- 更新后parent平衡因子==1/-1,说明parent所在子树的高度变化,影响了祖先,需要沿着祖先的路径往上更新
- 更新后parent的平衡因子==2/-2,说明parent所在子树出现了问题,已经失衡,需要对parent所在子树进行旋转,使其平衡
- 更新到根节点就可以结束了
代码如下
//更新平衡因子cur = newnode;while (parent){if (parent->_left == cur){parent->_bf--;}else if (parent->_right == cur){parent->_bf++;}else{assert(false);}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){//子树旋转break;}else{assert(false);}}
4. AVL树的插入之左旋
当一颗树(这棵树有可能是子树,也可能是一颗完整的树)插入一个结点以后,它会变为如下的形状(红色是插入的结点)
一开始,我们的树还是处于平衡状态的,但是当我们插入了一个结点以后,失去了平衡了,所以我们需要对其进行旋转使其进行平衡。
我们不难得知,上面两种树,都是右边的偏高,我们想,可以让cur作为这颗树的根节点,然后parent作为cur的左子树的话,那么就可以使得高度降低1次了。刚好使得我们的AVL树再次平衡了。
如下所示,当我们进行这样的旋转之后,我们会发现cur与parent的平衡因子都变为了0。
在这一步中,我们可以先思考一下为什么这样做了之后,parent和cur的平衡因子会变为了0呢?
其实是因为首先我们这里的情形是插入一个结点后,使得某一个平衡因子变为2,从而导致了失衡,那么既然要导致2,那么之前的parent就需要为1,那么自然parent左边和右边悬挂的两颗子树高度差就需要恰好为1。我们上面的情况就相当于是右边比左边高1。我们把多出来的那个结点称之为cur。
所以一开始的情形就得是parent的左子树,cur的左右子树高度是相同的,且都满足AVL树。然后我们才去插入一个结点。(如果cur的左右子树高度不相同,那么我们会发现,此时的cur应该称作为parent。如果parent的左子树太小,那么更不可能了,因为此时平衡因子早已为2了,早就需要调整了。如果parent的左子树要大一点,那么就不需要进行旋转了。)
当我们插入了这个结点以后,我们就需要控制平衡了,我们控制平衡的核心操作就是
parent->_right = cur->_left;
cur->_left = parent
上面两步操作执行完成之后,parent的左右子树高度一定是相同的,所以parent的平衡因子为0,而且cur的左子树的高度也变为了h+1,右子树由于插入了一个值所以也是h+1,恰好平衡因子也为0了。所以这样一来,使得树的高度整体降了一个且树平衡了。
上面的操作固然是很重要很核心的两步,不过要注意,我们的是三叉链模型,所以我们还需要改变_parent的指向,否则关系会十分混乱。而且还需要去考虑parent所代表的子树是不是一颗完整的树还是说一个子树。如果是子树那么又是左子树还是右子树呢?这些都是需要去考虑的。不过在上面的过程中,我们会发现,需要改变的结点一共就三个:parent,cur,cur->left这三个结点的指针需要进行修改。其他的结点,他们原本的位置是哪里,现在的相对位置仍然不变。
上面这种旋转方式其实就是由于右子树高了,所以需要向左旋转,即将一个结点给左边。我们将其称之为左旋
void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;Node* ppnode = parent->_parent;//修改parent的链接关系parent->_right = curleft;parent->_parent = cur;//修改curleft的链接关系if (curleft){curleft->_parent = parent;}//修改cur的链接关系cur->_left = parent;cur->_parent = ppnode;//修改ppnode与cur之间的关系if (ppnode == nullptr){_root = cur;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else if (ppnode->_right == parent){ppnode->_right = cur;}}parent->_bf = cur->_bf = 0;}
这样的话,就能保证在保证它是搜索树的条件下,还能降低这个子树的高度了
5. AVL树的左旋抽象图
如上是我们在左旋的情况下的抽象图,为什么要用一个抽象图呢?这是因为我们前面对于左旋的分析并不能完整的表示出左旋的全部情况。
在我们插入一个新的结点之前,a/b/c都是符合AVL规则的子树
当h为0的时候。
这种情况的左旋,我们前文已经讨论过
当h为1的时候
这种情况,还是前文所讨论的情形
但是当h为2的时候,我们每颗高度为2的子树,就分为以下几种情况了,如下的x,y,z三种都属于满足AVL规则的子树
结合上下两张图,在这里有一个细节需要注意:a,b是符合AVL规则的x/y/z,但是c一定是z,也只能是z。
这是因为,我们要保证插入新节点后,要导致根节点的平衡因子变为2。如果是x或者y型的话,无法这样的保证,要么会导致树仍然是一颗AVL树,就不需要进行旋转了。要么就是导致c自己内部的平衡因子变为了2,这样的话就与30这部分就无关了,提前进行了左旋。
这样的话,插入之前的树的可能性就有3*3*1=9种,而由于要插入的位置是z形状的,所以有四种可能,合计36种情况。
显然,我们不可能进行穷举,如果h更高,左旋的情况更加复杂。
所以我们的抽象图,就不在关心里面的具体细节了,我们只需要关心如下所示的几个结点直接的链接关系即可。
而这些链接关系,就是我们如下所示的代码
void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;Node* ppnode = parent->_parent;//修改parent的链接关系parent->_right = curleft;parent->_parent = cur;//修改curleft的链接关系if (curleft){curleft->_parent = parent;}//修改cur的链接关系cur->_left = parent;cur->_parent = ppnode;//修改ppnode与cur之间的关系if (ppnode == nullptr){_root = cur;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else if (ppnode->_right == parent){ppnode->_right = cur;}}parent->_bf = cur->_bf = 0; }
6.AVL树的右旋抽象图
右旋的情形与左旋的情形是非常之类似的,如下图所示,所谓右旋,就是左旋的镜像,左子树高了,所以就往右边旋转。
与左旋类似,它的三颗子树高度必须满足高度一样,否则就不是AVL树了,或者说插入一个结点之后还是平衡的,不是我们所期望的亚健康状态,即在插入一个结点后一定需要进行平衡了。
当h为0的时候,如下图所示
当h为1的时候,如下图所示
当h为2的时候,如下图所示是他们每一个子树的可能性。
其中a必须为z类型的子树,b,c可以为x,y,z均可
在h为2的时候,插入前的可能形状组合为3*3*1种,待插入位置有四种插法,可见合计36种。如果考虑的h更大,那么情况更加复杂,但是我们仍然只需要关心那四个最为关键的结点之间的关系即可
如下就是右旋的代码
void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;Node* ppnode = parent->_parent;//改变parent的链接关系parent->_left = curright;parent->_parent = cur;//改变curright的链接关系if (curright){curright->_parent = parent;}//改变cur的链接关系cur->_right = parent;cur->_parent = ppnode;//改变ppnode与cur之间的关系if (ppnode == nullptr){_root = cur;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else if (ppnode->_right == parent){ppnode->_right = cur;}}parent->_bf = cur->_bf = 0;}
7. AVL树的双旋
我们在前面的抽象图中,主要讨论的是如果是右子树高,继续往右子树的右孩子插入的话,就会诱发左旋
如果是左子树高的话,继续往左子树的左孩子插入新结点,就会诱发右旋
但是我们会发现还有可能会当右子树高的话,往右子树的左孩子插入新节点,或者左子树高的话,往左子树的右孩子插入新节点。这样的话,这颗树又该如何旋转呢?
如果我们还是按照先前的逻辑,右子树高的情况下,往右子树的左孩子插入结点,我们来试一下左旋是如何的进行的。(因为右边高,我们期望往左旋转)
当h为0的时候,下图第一个是我们上面提到的特殊情况,下图第二个是我们原先左旋就能解决的情况。可见这种类似于折线的情形无法使用左旋来完成
如果是h为1的情况下,就是如下图所示的情形。可见这种折线的情况也是不可以实现我们的目的的,这样的操作仅仅只是向右突出的折线变为了向左突出的折线。
并且我们发现,对上面的情形进行了右旋之后,又恢复了之前了状态。当时当我们先右旋,然后在左旋的时候,我们会惊讶的发现,树平衡了。
8. AVL树的右左双旋
前文说道,如果新结点插入的形状是折线形状的,单纯的左旋或者右旋已经无法解决问题了。而这个时候如果我们能够先进行右旋,然后左旋的话,那么问题将得到解决。我们来看抽象图
我们接下来对下面这棵树进行操作,先考虑右子树较高的情况
关于这棵树,我们将90的左子树进行了更细一步的划分,将一共结点单独的抽出来为我们的研究方便。
我们先说结论
以90为旋转点进行右旋,然后以30为旋转点进行左旋。就可以使得这棵树平衡
我们可以先画一下具象图来进行理解
当h==0的时候,即60这个结点就是新插入的结点,他的旋转图如下
当h==1的时候,这个结点可以在60的左边或者右边插入,都是满足条件的。因为都属于右凸出折线插入。仍然是先以90为旋转点进行右旋,然后以30为旋转点进行左旋。可见树平衡
当h==2的时候, 每个子树又呈现出了不同的情况
然而a和d一定是x/y/z中的任意一种,中间这颗子树一定是z形状的。由于我们已经进行了细分,所以中间这棵树我们直接画出具象图
我们的总变化共计3*3*4=36种
可是无论是如何的变化,我们都无需担心,因为只要我们先对90进行右旋,然后对30进行左旋,就一定可以使得这棵树变为平衡。
而诱发这种旋转的条件就是右子树原先比左子树高1,且新插入的结点在右子树的左子树中。当新节点插入以后,parent的平衡因子为2,cur的平衡因子为-1。这两个平衡因子的大小也就是诱发右左双旋的条件。
9. AVL树的右左双旋的本质
关于右左双旋,我们似乎可以直接复用前面的左旋和右旋的代码。但是这样做的话会出现问题的。因为只是左旋和右旋的话会导致平衡因子被改变为0,但是实际上,平衡因子不为0。也就是说平衡因子需要再次处理一下。
我们在分析一下右左双旋,当h==1的时候,我们当时可以插入在左边或者右边都是可以的
从中我们可以看到一个规律,也就是双旋的本质
60的左边变成了30的右边
60的右边变成了90的左边
60变成了这棵树的根
这样的话,我们在观察一下最终平衡因子的变化,似乎就区域于插入到了60的左边还是右边。即取决于插入后60的平衡因子。由于双旋的本质,所以最终插入结束以后,如果是插入到了60的左边,那么左边将被平衡为0,右边将被平衡为1。如果插入到了60的右边,左边被平衡为-1,右边被平衡为0。
如果60本身就是新插入的结点的话,那么最终三个结点都为0
最终我们得出了双选引发的平衡因子问题的解决方法
当插入到了60的右侧,那么最终的变化如下图所示
如果插入到了60的左侧,那么最终的变化如下图所示
如果是60本身就是被插入的,那么就是下图的变化
所以最终我们也可以写出右左双旋的完整代码了
10. AVL树的左右双旋
左右双旋与右左双旋是呈镜像关系的,我们这里直接画出他的抽象图。可见他的本质其实也是一样的。
那么我们就不难写出左右双旋的代码了
void RotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){curright->_bf = 0;cur->_bf = 0;parent->_bf = 0;}else if (bf == 1){curright->_bf = 0;cur->_bf = -1;parent->_bf = 0;}else if (bf == -1){curright->_bf = 0;cur->_bf = 0;parent->_bf = 1;}else{assert(false);}}
11. AVL树的验证
上面的程序都是我们在最好的情况下写出来的,但是大部分时候,我们写的代码各种bug,所以我们需要调试,在调试的过程中,自然免不了需要判断一棵树是否为AVL树。所以我们可以利用AVL树的规则,写出如下的代码判断一棵树是否为AVL树
int _Height(Node* root){if (root == nullptr){return 0;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? 1 + leftHeight : 1 + rightHeight;}bool _IsBalance(Node* root){if (root == nullptr){return true;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);if ((rightHeight - leftHeight) != root->_bf){cout << "平衡因子异常,当前插入结点为 " << root->_kv.first << endl;return false;}return abs(rightHeight - leftHeight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);}bool IsBalance(){return _IsBalance(_root);}
同时,当我们写完代码以后,我们可能不知道我们写的是否存在一些问题, 这个我们可以使用如下的测试用例去验证
void testAVL3()
{AVLTree<int, int> avl;srand(time(0));const int N = 10000;vector<int> v(N);for (int i = 0; i < N; i++){v.push_back(rand());avl.Insert(make_pair(v[i], v[i]));cout << avl.IsBalance() << endl;}
}
当运行结果全部为1的时候,说明我们的树没有任何问题
12. AVL树的删除
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与插入不同的是,删除节点后的平衡因子更新,如果更新后平衡因子是1或者-1,那么说明这棵树是不需要进行调整,如果平衡因子为0,说明原来是1或者-1,需要进行向上调整平衡因子。最差情况下一直要调整到根节点的位置 。他的更新策略与插入是相反的
如上图所示,是删除时候的模型,最终需要经历一个右旋。
可以看到上面的过程中,如果需要判断是否旋转的话,不是在其本身的路径上了,而是在另一颗树上判断是否需要旋转,而且旋转时候的条件判断将会更加复杂,双旋时候的平衡因子将更加复杂。所以这里就非常麻烦了。
三、AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即logN。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会变),可以考虑AVL树,但一个结构经常修改,就不太适合。
相关文章:
【C++从0到王者】第三十三站:AVL树
文章目录 前言一、AVL 树的概念二、AVL树的实现1. AVL树的结点定义2. AVL树的插入之插入部分3. AVL树的插入之平衡因子的改变4. AVL树的插入之左旋5. AVL树的左旋抽象图6.AVL树的右旋抽象图7. AVL树的双旋8. AVL树的右左双旋9. AVL树的右左双旋的本质10. AVL树的左右双旋11. AV…...
手机机型响应式设置2
window.screen.height:屏幕高度 window.innerHeight:视口高度(去除浏览器头尾的高度) document.body.clientHeight:内容高度 vh:网页视口高度的1/100 vw:网页视口宽度的1/100 vmaxÿ…...
uni-app 之 解决u-button始终居中问题
uView中u-button始终居中问题如何解决的简单方法? 1:给该元素margin-right: 0;可以达到向右靠齐; 2:给该元素的父元素设置float: right image.png <u-button style"width: 50px; margin-left: 0;" plain"t…...
Python日期处理库:掌握时间的艺术
💂 个人网站:【工具大全】【游戏大全】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 日期和时间在计算机编程…...
JOSEF约瑟 智能电流继电器KWJL-20/L KWLD26 零序孔径45mm 柜内导轨式安装
KWJL-20智能电流继电器 零序互感器: KWLD80 KWLD45 KWLD26 KWJL-20 一、产品概述 KWJL-20系列智能剩余电流继电器(以下简称继电器)适用于交流电压至660V或更高的TN、TT、和IT系统,频率为50Hz。通过零序电流互感器检测出超过…...
NLP技术如何为搜索引擎赋能
目录 1. NLP关键词提取与匹配在搜索引擎中的应用1. 关键词提取例子 2. 关键词匹配例子 Python实现 2. NLP语义搜索在搜索引擎中的应用1. 语义搜索的定义例子 2. 语义搜索的重要性例子 Python/PyTorch实现 3. NLP个性化搜索建议在搜索引擎中的应用1. 个性化搜索建议的定义例子 2…...
演唱会没买到票?VR直播为你弥补遗憾
听说周杰伦开了演唱会?没买到票的人是不是有着大大的遗憾呢?很多时候大型活动、演唱会都会因为场地限制而导致很多人未能有缘得见,而且加上票价成本高,“黄牛票”事件频出,我们的钱包受不住啊!!…...
myabtis的缓存级别
文章目录 MyBatis缓存的区别是什么作用范围方面有哪些差异生命周期数据进行了存储缓存的优缺点 MyBatis缓存的区别是什么 MyBatis 提供了一级缓存和二级缓存,这两者的主要区别在于其作用范围和生命周期。 一级缓存:一级缓存是 SqlSession 级别的缓存。…...
gin框架再探
Gin框架介绍及使用 | 李文周的博客 (liwenzhou.com) lesson03_gin框架初识_哔哩哔哩_bilibili 1.路由引擎 //路由引擎 rgin.Default() 2.一些http请求方法 get post put delete等等 遇到什么路径,执行什么函数 r.GET("/hello",func{做你想做的事返回…...
经典算法-----约瑟夫问题(C语言)
目录 前言 故事背景 约瑟夫问题 环形链表解决 数组解决 前言 今天我们来玩一个有意思的题目,也就是约瑟夫问题,这个问题出自于欧洲中世纪的一个故事,下面我们就去通过编程的方式来解决这个有趣的问题,一起来看看吧!…...
代码随想录 动态规划Ⅴ
494. 目标和 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - ,然后串联起所有整数,可以构造一个 表达式 : 例如,nums [2, 1] ,可以在 2 之前添加 ,在 1 之前添加 - …...
驱动DAY9
驱动文件 #include <linux/init.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/fs.h> #include <linux/io.h> #include <linux/device.h> #incl…...
03贪心:摆动序列
03贪心:摆动序列 376. 摆动序列 局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。 整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。…...
javascript获取元素在浏览器中工作区域的左、右、上、下距离,或带滚动条的元素在页面中的大小
//获取元素在包含元素框中的大小 //第1个函数为获取元素在包含元素中左内边框的距离 function getELementLeft(element){//获取元素在包含元素左边距离var actualeftelement.offsetLeft;//获取元素的上级包含元素var currentelement.offsetParent;//循环到一直没有包含元素whil…...
VSCode 安装使用教程 环境安装配置 保姆级教程
一个好用的 IDE 不仅能提升我们的开发效率,还能让我们保持愉悦的心情,这样才是非常 Nice 的状态 ^_^ 那么,什么是 IDE 呢 ? what IDE(Integrated Development Environment,集成开发环境)是含代码…...
c盘中temp可以删除吗?appdata\local\temp可以删除吗?
http://www.win10d.com/jiaocheng/22594.html C盘AppData文件夹是一个系统文件夹,里面存储着临时文件,各种应用的自定义设置,快速启动文件等。近期有用户发现appdata\local\temp占用了大量的空间,那么该文件可以删除吗?…...
Java手写聚类算法
Java手写聚类算法 1. 算法思维导图 以下是聚类算法的实现原理的思维导图,使用Mermanid代码表示: #mermaid-svg-AK9EgYRS38PkRJI4 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-AK9EgYRS38…...
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
目录 CAS什么是CASCAS的应用ABA问题异常举例 Synchronized 原理基本特征加锁过程偏向锁轻量级锁重量级锁 其他优化操作锁消除锁粗化 CAS 什么是CAS CAS: 全称Compare and swap,字面意思:”比较并交换“,CAS涉及如下操作: 假设内存中的原数据…...
solid works草图绘制与设置零件特征的使用说明
(1)草图绘制 • 草图块 在 FeatureManager 设计树中,您可以隐藏和显示草图的单个块。您还可以查看块是欠定义 (-)、过定义 () 还是完全定义。 要隐藏和显示草图的单个块,请在 FeatureManager 设计树中右键单击草图块,…...
vue3使用router.push()页面跳转后,该页面不刷新问题
文章目录 原因分析最优解决 原因分析 这是一个常见问题,当使用push的时候,会向history栈添加一个新记录,这个时候,再添加一个完全相同的路由时,就不会再次刷新了 最优解决 在页面跳转时加上params参数时间 router.…...
如何理解数字工厂管理系统的本质
随着科技的飞速发展和数字化转型的推动,数字工厂管理系统逐渐成为工业4.0时代的重要工具。数字工厂系统旨在整合和优化工厂运营的各个环节,通过实时数据分析和处理,提升生产效率,降低成本,并增强企业的整体竞争力。为了…...
笔记1.3 数据交换
如何实现数据通过网络核心从源主机到达目的主机? 数据交换 交换网络: 动态转接动态分配传输资源 数据交换类型: (1)电路交换 (2)报文交换 (3)分组交换 电路交换的特…...
实时车辆行人多目标检测与跟踪系统(含UI界面,Python代码)
算法架构: 目标检测:yolov5 目标跟踪:OCSort其中, Yolov5 带有详细的训练步骤,可以根据训练文档,训练自己的数据集,及其方便。 另外后续 目标检测会添加 yolov7 、yolox,目标跟踪会…...
谷歌AI机器人Bard发布强大更新,支持插件功能并增强事实核查;全面整理高质量的人工智能、机器学习、大数据等技术资料
🦉 AI新闻 🚀 谷歌AI机器人Bard发布强大更新,支持插件功能并增强事实核查 摘要:谷歌的人工智能聊天机器人Bard发布了一项重大更新,增加了对谷歌应用的插件支持,包括 Gmail、Docs、Drive 等,并…...
NI SCXI-1125 数字量控制模块
NI SCXI-1125 是 NI(National Instruments)生产的数字量控制模块,通常用于工业自动化和控制系统中,以进行数字输入和输出控制。以下是该模块的一些主要产品特点: 数字量输入:SCXI-1125 模块通常具有多个数字…...
链表oj题1(Leetcode)——移除链表元素,反转链表,链表的中间节点,
链表OJ 一,移除链表元素1.1分析1.2代码 二,找到链表的中间节点2.1分析2.2代码 三,反转链表3.1分析3.2代码 四,找到链表中倒数第k个节点4.1分析4.2代码 一,移除链表元素 移除链表元素 1.1分析 这里的删除要分成两种…...
【libuv】与uvgrtrp的_SSIZE_T_定义不同
libuv的 #if !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED) typedef intptr_t ssize_t;...
安卓ROM定制 修改必备常识-----初步了解system系统分区文件夹的基本含义 【二】
安卓修改rom 固件 修改GSI 移植rom 必备常识 lib--**so文件基本解析 一起来了解system目录相应文件的用途吧。(rom版本不同里面的app也会不一样) 简单打开img格式后缀文件 给大家说下最简单的方法提取img里面的文件,对于后缀img格式的文件可…...
GPT会统治人类吗
一 前言 花了大概两天时间看完《这就是ChatGPT》,触动还是挺大的,让我静下来,认真地想一想,是否真正理解了ChatGPT,又能给我们以什么样的启发。 二 思考 在工作和生活中,使用ChatGPT或文心一言,…...
win系统环境搭建(六)——Windows安装nginx
windows环境搭建专栏🔗点击跳转 win系统环境搭建(六)——Windows安装nginx 本系列windows环境搭建开始讲解如何给win系统搭建环境,本人所用系统是腾讯云服务器的Windows Server 2022,你可以理解成就是你用的windows10…...
深圳建网站兴田德润优秀/北京推广
Adobe Photoshop是目前最流行的平面设计软件之一。可以说,只要你接触平面设计,那么无论早晚,你都要和它打交道。关于Photoshop,要说的实在太多太多,但不论你想让它成为你的左膀右臂,或者仅仅是用它来做一些…...
贵阳网站建设蜜蜂/torrentkitty磁力猫引擎
https://www.cnblogs.com/liyasong/p/saoma.html...
最新章节 第四百六十二章 花两亿做的网站/店铺推广软文案例
磁盘:磁盘上有很多小的颗粒点,向上是N为1,向上是S为0,要想让磁盘产生高低电压,就要给他一个初始电压 磁盘发生位移,切割磁感线,产生高低电压,传送到内存中,内存在传送到…...
做网站需要备案吗/沈阳网站关键词优化多少钱
一、定位 定位:定位是一种更加高级的布局手段。通过定位可以将元素放在页面的任意位置。position:通过position来设置定位。可选值:static 默认值,元素是静止,没有开启定位relative 开启元素的相对定位absolute 开启元素的绝对…...
外贸做网站要多久做好/俄罗斯搜索引擎yandex推广
原文:redis 系列26 Cluster高可用 (1)一.概述 Redis集群提供了分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。在大数据量方面的高可用方案,cluster集群比Sentinel有优势。但Redis集群并不支持处理多个keys的命令,因…...
企商网站建设/深圳seo外包
DHT11原理:https://blog.csdn.net/x1131230123/article/details/103665953 MSP430 G2553: MSP430 F5529: 串口端:...