AVL 树的理解和简单实现

目录

1. AVL 树

1.1. AVL 树的概念

1.2. AVL 树的性质

2. AVL 树的框架如下 

2. AVL树的 插入

2.1. 平衡因子的更新

2.2.1. 平衡因子更新的第一种情况

2.2.2. 平衡因子更新的第二种情况

2.2.3. 平衡因子更新的第三种情况

2.2.4. 平衡因子更新的代码框架如下

2.2. AVL 树的旋转

2.2.1. 左单旋

2.2.2. 右单旋

2.2.3. 左右双旋

2.2.4. 右左双旋

2.3. 验证 AVL 树的插入 

2.4. AVL 树插入的完整实现


1. AVL 树

1.1. AVL 树的概念

AVL树是一种自平衡的二叉搜索树,它以其发明者 G.M.Adelson-Velsky 和 E.M.Landis 的名字命名。AVL树保持树的左右子树高度的平衡,使得树的整体高度相对较小,提供了快速的查找、插入和删除操作。

在 AVL 树中,每个节点都包含一个键值对,且满足以下性质:

  • 左子树中的所有节点的键都小于该节点的键;
  • 右子树中的所有节点的键都大于该节点的键;
  • 每个节点的左子树和右子树的高度差(平衡因子)最多为 1。

为了维持这种平衡,AVL树在每次插入或删除节点时会根据需要进行旋转操作。旋转操作包括左旋和右旋,并通过重新分配节点的位置使得AVL树重新平衡。这种自平衡的机制保证了AVL树的高度始终保持在较小的范围内,时间复杂度为O(log n)。

相对于其他平衡二叉搜索树(如红黑树),AVL树的平衡因子要求更为严格不允许有任何节点的平衡因子绝对值超过1。这使得AVL树的修改操作更加频繁,但在查询操作上性能优于红黑树。

总结来说,AVL树是一种自平衡二叉搜索树,通过保持树的左右子树高度的平衡来提供快速的查找、插入和删除操作。它在每个节点上维护了平衡因子,并通过旋转操作来保持树的平衡性。这种自平衡的特性使得AVL树在一些对插入和删除操作要求较频繁的场景中具有优势。

1.2. AVL 树的性质

AVL树具有以下几个性质:

  • 二叉搜索树:AVL树是一种二叉搜索树,需要满足以下性质:
    • 左子树中的所有节点的键都小于该节点的键;

    • 右子树中的所有节点的键都大于该节点的键;

    • 左右子树都是二叉搜索树。

  • 平衡性:AVL树的关键特点是保持平衡,即每个节点的左子树和右子树的高度差(平衡因子)最多为 1,确保了树的整体高度相对较小;
    • 平衡因子:每个节点都有一个平衡因子,定义为右子树的高度减去左子树的高度(或左子树的高度减去右子树的高度)。平衡因子只能为 -1、0 或 1。
  • 自平衡:当进行插入或删除操作导致AVL树不再平衡时,AVL树会通过旋转操作来恢复平衡。旋转操作包括左旋和右旋以及左右双旋和右左双旋,并通过重新分配节点的位置调整树的结构,使得树重新达到平衡状态;
  • 高效性:由于AVL树的平衡性,其高度相对较小,从而保证了查找、插入和删除操作的平均时间复杂度为 O(log n)。

综上所述,AVL树是一种平衡二叉搜索树,具有二叉搜索树的性质,同时通过自平衡保持平衡性。它的平衡性和高效性使得它在某些场景中具有优势。

注意:AVL树不一定有平衡因子(balance factor),我们在这里使用平衡因子只是它的一种实现方式,并且在这里我们的平衡因子 =  右子树的高度 - 左子树的高度。

2. AVL 树的框架如下 

如下图所示:这就是一颗AVL树

我们在这里实现的 AVL 树采用三叉链的形式,大致框架如下:

namespace Xq
{template<class K,class V>struct avl_tree_node{// 采用三叉链的形式avl_tree_node<K,V>* _left;avl_tree_node<K,V>* _right;avl_tree_node<K,V>* _parent;std::pair<K,V> _kv;int _bf;  // balance factor 平衡因子avl_tree_node(const std::pair<K,V>& kv = std::pair<K,V>()):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0)  {}};template<class K,class V>class avl_tree{private:typedef avl_tree_node<K,V> Node;public:avl_tree(Node* root = nullptr):_root(root){}bool insert(const std::pair<K,V>& kv){}private:private:Node* _root;};
}

2. AVL树的 插入

AVL树的插入元素,也符合普通二叉搜索树的原则,找到合适的位置进行插入元素。

我们可以将AVL树的insert分为三个过程:

  1. 找到合适位置;
  2. 构造新节点,并完成连接关系;
  3. 更新平衡因子,如果平衡因子的绝对值 > 1,需要进行旋转处理。

2.1. 平衡因子的更新

平衡因子的更新:

  • 如果平衡因子更新的整个过程,平衡因子没有出现问题 (平衡因子的绝对值 |bf|<= 1),那么说明插入对AVL树的平衡结构不影响,不需要处理;
  • 如果平衡因子更新的整个过程中,一旦平衡因子出现问题 (平衡因子的绝对值大于1),平衡结构受到影响,需要停止更新,并进行旋转处理。

旋转分为四种:左旋、右旋、左右双旋、右左双旋。

插入新增节点,会影响祖先的 bf (全部或者部分祖先)。

平衡因子的更新有三种情况

那么,什么决定了平衡因子是否要继续往上更新?取决于 parent 的所在的子树高度(即左右子树高度的较大值)是否变化?

如果变了 (例如会影响祖先的平衡因子(全部或部分)) 继续更新,不变则不再更新。

假设 cur 是新增节点,那么:

  • 如果 cur == parent->right  那么父亲 parent 的平衡因子bf++;
  • 如果 cur == parent->left    那么父亲 parent 的平衡因子bf--。

注意:插入之前我们需要保证原树是一颗AVL树,因此插入之前所有节点的|bf| <= 1。

2.2.1. 平衡因子更新的第一种情况

平衡因子更新后: parent->bf == 1 || parent->bf == -1;

此时说明 parent 所在的子树的高度变了,继续向上更新,为什么?

因为插入元素之后 parent 的 bf==1 或者 bf ==-1,那么说明插入之前 parent->bf == 0,说明原左右子树的高度相等,现在有一边的子树高度高1,说明 parent 一边高一边低,高度变了,继续向上更新。

如图所示:

2.2.2. 平衡因子更新的第二种情况

平衡因子更新后:parent->bf == 0;

此时说明 parent 所在的子树高度不变,不用继续往上更新,这一次插入了就结束。

因为插入元素之后 parent 的 bf == 0,那么说明插入之前 parent->bf == 1 || parent->bf == -1,即插入之前一边高一边低,新增节点插入在矮的那边,插入之后,左右子树高度相同,parent 的高度不变,因此此时平衡因子不用向上更新了,插入结束。

如图所示:

2.2.3. 平衡因子更新的第三种情况

平衡因子更新后:parent->bf == -2 || parent->bf == 2

说明插入新增节点后 parent 的所在的子树不平衡(|bf| >= 2),因此需要停止向上更新,处理这颗子树,如何处理?旋转处理,处理完插入就结束。

如图所示:

2.2.4. 平衡因子更新的代码框架如下

while (parent)
{if (cur == parent->_left)--parent->_bf;else++parent->_bf;//case 1:  继续向上更新if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;continue;}// case 2: 符合AVL树,结束更新else if (parent->_bf == 0){break;}// case 3: 当前子树出现问题了(|bf| >= 2),需要停止更新,处理当前AVL子树,处理后,插入结束else if (parent->_bf == 2 || parent->_bf == -2){// 旋转处理:break;}// case 4:非法情况,说明前面的AVL树不符合规则else{// 直接断死assert(false);}
}

2.2. AVL 树的旋转

旋转操作是为了保持或恢复AVL树的平衡性。

AVL树的平衡在于每个节点的左子树和右子树的高度差(平衡因子)最多为 1。当进行插入或删除操作后,可能会打破原本的平衡性,导致某个节点的平衡因子超过了允许的范围。

为了恢复平衡,AVL树采用不同类型的旋转操作,包括左旋和右旋。旋转操作通过重新分配节点的位置,使得树重新达到平衡状态,确保每个节点的平衡因子保持在允许的范围内。

旋转操作的具体目的如下:

  • 1. 左旋:当一个节点的右子树高度大于左子树高度时 (高度相差 >= 2),进行左旋操作。左旋将当前节点和其右子节点进行交换,使得原先的右子节点成为新的根节点,同时保证原来左子树不变、右子节点的左子树作为新的右子树,从而降低了树的高度差;
  • 2. 右旋:当一个节点的左子树高度大于右子树高度时 (高度相差 >= 2),进行右旋操作。右旋将当前节点和其左子节点进行交换,使得原先的左子节点成为新的根节点,同时保证原来右子树不变、左子节点的右子树作为新的左子树,从而降低了树的高度差。

通过旋转操作,AVL树可以在插入或删除节点后自动调整自己,降低AVL树的高度,维持其平衡结构 ,提供更高效的查找、插入和删除操作。

总结来说,旋转操作的目的是为了保持或恢复AVL树的平衡性,通过重新分配节点的位置降低树的高度差,确保每个节点的平衡因子在允许范围内。这样可以提供更好的性能和效率。

  • 旋转处理的情况分为四种:左单旋、右单旋、左右双旋、右左双旋;
  • 旋转的原则:旋转后仍是一颗AVL树;
  • 旋转的目的:左右均衡,降低整棵AVL树的高度。

我们依次来看:

2.2.1. 左单旋

如下图所示:

当h==0时,情况如下:

当h==1时,情况如下: 

当h == 2时,情况如下: 

上面列出了三种情况,当然远远不止,虽然有很多种情况,但是对于AVL树的左单旋的处理方式是固定的:

只要满足 cur->bf == 2 && cur_right->bf == 1那么就对cur进行左单旋。

旋转后将 cur 和 cur_right 的平衡因子置为0即可。

具体如下图所示:

有了上面的分析,我们可以得出结论:

当cur->bf == 2 && cur_right->bf == 1那么就对cur进行左单旋

旋转后更新平衡因子:cur->bf = 0;  cur_right->bf = 0;

因此,我们的左单旋实现如下 :

为了更好地实现左单旋,我们借助下面的图来实现

实现代码如下:

void left_rotate(Node* parent)
{// 确立四个节点的初始位置Node* cur = parent;Node* cur_right = cur->_right;Node* cur_right_left = cur_right->_left;Node* cur_parent = cur->_parent;cur->_right = cur_right_left;// 当h == 0时,cur_right_left是为空的,因此在这里要判断一下if (cur_right_left)cur_right_left->_parent = cur;cur_right->_left = cur;cur->_parent = cur_right;// 如果parent是根节点,那么cur_right就是新根if (!cur_parent){cur_right->_parent = nullptr;_root = cur_right;}// 如果parent不是根节点,那么cur_parent不为空else{if (cur_parent->_kv.first > cur_right->_kv.first){cur_parent->_left = cur_right;}else{cur_parent->_right = cur_right;}cur_right->_parent = cur_parent;}// 更新cur和cur_right的平衡因子cur_right->_bf = cur->_bf = 0;
}

2.2.2. 右单旋

对于右单旋来说,分析思路与左单旋差别不大,只不过右单旋是单纯的左边高,进行右单旋,在这里只以 h==1 的具象图和抽象图用以举例说明:

当h==1时,如下图所示:

右单旋的抽象图,如下图所示:

与左单旋同样,虽然右单旋会有很多种情况,但是它们的处理方式是一样的,只要满足

cur->bf == -2 && cur_left->bf == -1 就对cur进行右单旋,旋转玩后将 cur 和 cur_left 的 bf 更新为0,旋转结束。

有了上面的分析,我们可以得出结论:

当cur->bf == -2 && cur_left->bf == -1那么就对cur进行右单旋

旋转后更新平衡因子cur->bf = 0; cur_left->bf = 0;

因此,我们的右单旋实现如下 :

为了更好地实现右单旋,我们借助下面的图来实现:

右单旋实现代码如下: 

void right_rotate(Node* parent)
{// 确立四个节点的初始位置Node* cur = parent;Node* cur_left = cur->_left;Node* cur_left_right = cur_left->_right;Node* cur_parent = cur->_parent;cur->_left = cur_left_right;// 当h == 0时,cur_left_right为空,因此在这里要判断一下if (cur_left_right)cur_left_right->_parent = cur;cur_left->_right = cur;cur->_parent = cur_left;// 如果cur_parent为空,那么cur_left就是新根if (!cur_parent){cur_left->_parent = nullptr;_root = cur_left;}else{// 在这里需要判断一下kv.first的大小,以确定cur_left是左孩子还是右孩子if (cur_parent->_kv.first > cur_left->_kv.first){cur_parent->_left = cur_left;}else{cur_parent->_right = cur_left;}// 最后也要链接父亲cur_left->_parent = cur_parent;}// 更新平衡因子cur->_bf = cur_left->_bf = 0;
}

2.2.3. 左右双旋

左右双旋:‘例如这种形状<’,整体看是左边高,但是左子树又是右边高。

此时需要:先对左子树进行左单旋、在对整体进行右单旋。

先左单旋的目的是:让这棵AVL子树变成单纯的左边高,在进行右单旋。

如图所示:

当h == 0时,如下图所示 

当 h == 1时,如下图说式:

左右双旋的抽象图:

 

将上面的图联系到一起,我们可以发现,它们的旋转方式和旋转条件是一样的。

旋转条件: cur->bf == -2 && cur_left->bf == 1  (对应到上图:(100就是cur,  50就是cur_left) )

旋转方式:先对左子树进行左单旋,在对整体进行右单旋

但是最后的平衡因子的更新却不一样,可以分为三种情况

当插入元素后:

  • case 1:cur_left_right->bf == 0
    • 旋转后:cur->bf = 0; cur_left->bf = 0; cur_left_right->_bf = 0;
  • case 2:cur_left_right->bf == -1
    • ​​​​​​​​​​​​​​旋转后:cur->bf = 1; cur_left->bf = 0; cur_left_right->_bf = 0;
  • case 3:cur_left_right->bf == 1
    • ​​​​​​​​​​​​​​旋转后:cur->bf = 0; cur_left->bf = -1; cur_left_right->_bf = 0;

左右双旋的代码如下:

void left_right_rotate(Node* parent)
{Node* cur = parent;Node* cur_left = cur->_left;Node* cur_left_right = cur_left->_right;int bf = cur_left_right->_bf;// 先左旋、后右旋left_rotate(cur_left);right_rotate(cur);if (bf == 0){cur->_bf = 0;cur_left->_bf = 0;cur_left_right->_bf = 0;}else if (bf == -1){cur->_bf = 1;cur_left->_bf = 0;cur_left_right->_bf = 0;}else if (bf == 1){cur->_bf = 0;cur_left->_bf = -1;cur_left_right->_bf = 0;}else{// 非法情况,直接断死assert(false);}
}

2.2.4. 右左双旋

右左双旋的分析思路和左右双旋没有太大差异。在这里只以h == 0 和 对应的抽象图举例分析其中细节。

如图所示,这就是右左双旋的抽象图:

当h == 0时的具象图,如下图所示:

剩下两种情况的抽象图,如下图所示:

将上面的图联系到一起,我们可以发现,他们的旋转方式和旋转条件是一样的。

旋转条件: cur->bf == 2 && cur_right->bf == -1 (对应到上图:(50就是cur,100就是cur_right)) 

旋转方式:先对右子树进行右单旋,在对整体进行左单旋。

但是最后的平衡因子的更新却不一样,可以分为三种情况

当插入元素后:

  • case 1:cur_right_left->bf == 0
    • ​​​​​​​​​​​​​​旋转后:cur->bf = 0;cur_right->bf = 0; cur_right_left->bf = 0;
  • case 2:cur_right_left->bf == -1
    • ​​​​​​​​​​​​​​旋转后:cur->bf = 0;cur_right->bf = 1; cur_right_left->bf = 0;
  • case 3:cur_right_left->bf == 1
    • ​​​​​​​​​​​​​​旋转后:cur->bf = -1;cur_right->bf = 0; cur_right_left->bf = 0;

右左双旋代码如下:

void right_left_rotate(Node* parent)
{Node* cur = parent;Node* cur_right = cur->_right;Node* cur_right_left = cur_right->_left;int bf = cur_right_left->_bf;right_rotate(cur_right);left_rotate(cur);if (bf == 0){cur->_bf = 0;cur_right->_bf = 0;cur_right_left->_bf = 0;}else if (bf == -1){cur->_bf = 0;cur_right->_bf = 1;cur_right_left->_bf = 0;}else if (bf == 1){cur->_bf = -1;cur_right->_bf = 0;cur_right_left->_bf = 0;}else{// 非法情况,直接断死assert(false);}
}

2.3. 验证 AVL 树的插入 

如何验证:

我们需要验证每一棵 AVL 子树的左右子树高度差是否小于等于1,且要判断每个节点的平衡因子是否等于当前节点的左右子树的高度差。

代码实现:

// 得到子树的高度
int _get_tree_high(Node* root)
{if (!root)return 0;else{int left_high = _get_tree_high(root->_left);int right_high = _get_tree_high(root->_right);return left_high > right_high ? ++left_high : ++right_high;}
}bool _is_balance_tree(Node* root)
{// 空树可以认为是AVL树if (!root)return true;else{// 左子树的高度int left_high = get_tree_high(root->_left);// 右子树的高度int right_high = get_tree_high(root->_right);// 如果当前节点的平衡因子不等于当前节点的左右子树的高度差,说明异常if (right_high - left_high != root->_bf){std::cout << root->_kv.first << " : 该节点的平衡因子出现异常" << std::endl;return false;}// 计算每颗AVL子树的左右子树高度差,如果存在大于1的情况,说明异常int bf = right_high - left_high;if (bf < 0)bf *= -1;return bf <= 1&& _is_balance_tree(root->_left)&& _is_balance_tree(root->_right);}
}

2.4. AVL 树插入的完整实现

#pragma once
#include <iostream>
#include <assert.h>
#include <queue>
#include <vector>
#include <utility>
#include <time.h>namespace Xq
{template<class K,class V>struct avl_tree_node{avl_tree_node<K,V>* _left;avl_tree_node<K,V>* _right;avl_tree_node<K,V>* _parent;std::pair<K,V> _kv;int _bf;  // balance factoravl_tree_node(const std::pair<K,V>& kv = std::pair<K,V>()):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}};template<class K,class V>class avl_tree{private:typedef avl_tree_node<K,V> Node;public:avl_tree(Node* root = nullptr):_root(root){}bool insert(const std::pair<K,V>& kv){if(_root == nullptr){_root = new Node(kv);return true;}else{Node* cur = _root;Node* parent = nullptr;while(cur){if(cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if(cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);if(kv.first > parent->_kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}// 调整平衡因子while(parent){if(cur == parent->_left)--parent->_bf;else++parent->_bf;//case 1:  继续向上更新if(parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;continue;}// case 2: 符合AVL树,结束更新else if(parent->_bf == 0){break;}// case 3: 当前子树出现问题了(|bf| >= 2),需要停止更新,处理当前AVL子树,处理后,插入结束else if(parent->_bf == 2 || parent->_bf == -2){// 旋转处理://case 1: 左单旋if(parent->_bf == 2 && parent->_right->_bf == 1){left_rotate(parent);}//case 2: 右单旋else if(parent->_bf == -2 && parent->_left->_bf == -1){right_rotate(parent);}//case 3:左右双旋else if(parent->_bf == -2 && parent->_left->_bf == 1){left_right_rotate(parent);}//case 4:右左双旋else if(parent->_bf == 2 && parent->_right->_bf == -1){right_left_rotate(parent);}//非法情况,断死else {assert(false);}break;}// case 4:非法情况,说明前面的AVL树不符合规则else{// 直接断死assert(false);}}return true;}}void left_rotate(Node* parent){// 1. 确立四个节点的初始位置Node* cur = parent;Node* cur_right = cur->_right;Node* cur_right_left = cur_right->_left;Node* cur_parent = cur->_parent;cur->_right = cur_right_left;// 当h == 0时,cur_right_left是为空的,因此在这里要判断一下if(cur_right_left)cur_right_left->_parent = cur;cur_right->_left = cur;cur->_parent = cur_right;// 如果parent是根节点,那么cur_right就是新根if(!cur_parent){cur_right->_parent = nullptr;_root = cur_right;}// 如果parent不是根节点,那么cur_parent不为空else{if(cur_parent->_kv.first > cur_right->_kv.first){cur_parent->_left = cur_right;}else{cur_parent->_right = cur_right;}cur_right->_parent = cur_parent;}cur->_bf = cur_right->_bf = 0;}void right_rotate(Node* parent){// 确立四个节点的初始位置Node* cur = parent;Node* cur_left = cur->_left;Node* cur_left_right = cur_left->_right;Node* cur_parent = cur->_parent;cur->_left = cur_left_right;// 当h == 0时,cur_left_right为空,因此在这里要判断一下if(cur_left_right)cur_left_right->_parent = cur;cur_left->_right = cur;cur->_parent = cur_left;// 如果cur_parent为空,那么cur_left就是新根if(!cur_parent){cur_left->_parent = nullptr;_root = cur_left;}else{if(cur_parent->_left == cur){cur_parent->_left = cur_left;}else{cur_parent->_right = cur_left;}cur_left->_parent = cur_parent;}// 更新平衡因子cur->_bf = cur_left->_bf = 0;}void left_right_rotate(Node* parent){Node* cur = parent;Node* cur_left = cur->_left;Node* cur_left_right = cur_left->_right;int bf = cur_left_right->_bf;// 先左旋、后右旋left_rotate(cur_left);right_rotate(cur);if(bf == 0){cur->_bf = 0;cur_left->_bf = 0;cur_left_right->_bf = 0;}else if(bf == -1){cur->_bf = 1;cur_left->_bf = 0;cur_left_right->_bf = 0;}else if(bf == 1){cur->_bf = 0;cur_left->_bf = -1;cur_left_right->_bf = 0;}else {// 非法情况,直接断死assert(false);}}void right_left_rotate(Node* parent){Node* cur = parent;Node* cur_right = cur->_right;Node* cur_right_left = cur_right->_left;int bf = cur_right_left->_bf;right_rotate(cur_right);left_rotate(cur);if(bf == 0){cur->_bf = 0;cur_right->_bf = 0;cur_right_left->_bf = 0;}else if(bf == -1){cur->_bf = 0;cur_right->_bf = 1;cur_right_left->_bf = 0;}else if(bf == 1){cur->_bf = -1;cur_right->_bf = 0;cur_right_left->_bf = 0;}else {// 非法情况,直接断死assert(false);}}void level_order(){_level_order(_root);}int get_tree_high(Node* root){return _get_tree_high(root);}bool is_balance_tree(){return _is_balance_tree(_root);}int in_outside_get_tree_high(){return _get_tree_high(_root);}private:void _level_order(Node* root){if(!root)return ;else{std::queue<Node*> qu;qu.push(root);while(!qu.empty()){Node* front = qu.front();qu.pop();if(front){qu.push(front->_left);qu.push(front->_right);}if(!front)std::cout << "N ";elsestd::cout << front->_kv.first << " ";}std::cout << std::endl;}}int _get_tree_high(Node* root){if(!root)return 0;else{int left_high = _get_tree_high(root->_left);int right_high = _get_tree_high(root->_right);return left_high > right_high ? ++left_high : ++right_high;}}bool _is_balance_tree(Node* root){// 空树可以认为是AVL树if(!root)return true;else{int left_high = get_tree_high(root->_left);int right_high = get_tree_high(root->_right);if(right_high - left_high != root->_bf){std::cout << root->_kv.first <<" : 该节点的平衡因子出现异常" << std::endl;return false;}// 计算每颗AVL子树的左右子树高度差,如果存在大于1的情况,说明异常int bf = right_high-left_high;if(bf < 0)bf *= -1;return bf <= 1 && _is_balance_tree(root->_left)&& _is_balance_tree(root->_right);}}private:Node* _root;};
}

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

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

相关文章

《C++学习笔记---初阶篇6》---string类 上

目录 1. 为什么要学习string类 1.1 C语言中的字符串 2. 标准库中的string类 2.1 string类(了解) 2.2 string类的常用接口说明 2.2.1. string类对象的常见构造 2.2.2. string类对象的容量操作 2.2.3.再次探讨reserve与resize 2.2.4.string类对象的访问及遍历操作 2.2.5…

5KVA电力高频逆变器DU5000HD不间断电源DU3000HD

UPS电力高频逆变器DU3000HD不间断电源模块DU5000HD&#xff0c;单机版2KVA逆变电源DU2000HD&#xff0c;并机版2KVA逆变器DU2000H&#xff0c;3KVA逆变装置DU3000H&#xff0c;DU5000H&#xff0c;IV2000HD-2&#xff0c;IV3000HD-2&#xff0c;IV5000HD-2&#xff0c;IV2000H-…

基于51单片机音乐倒计时倒数计数器设计( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机音乐倒计时设计( proteus仿真程序设计报告原理图讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0090 1. 主要功能&#xff1a; 基于51单片机的音乐倒计时器设计 设计内…

47.乐理基础-音符的组合方式-连线

连线与延音线长得一模一样 它们的区别就是延音线的第三点&#xff0c;延音线必须连接相同的音 连线在百分之九十九的情况下&#xff0c;连接的是不同的音&#xff0c;如下图的对比&#xff0c;连线里的百分之1&#xff0c;以现在的知识无法理解&#xff0c;后续再写 在乐谱中遇…

解决在C#中方向键对控件焦点的控制

不要犹豫直接把下面这个程序复制进去就好了&#xff0c;不用担心0个引用&#xff0c;哈哈&#xff0c;可以的 public partial class MainForm : Form {public MainForm(){InitializeComponent();}protected override bool ProcessDialogKey(Keys keyData){// 检查是否是方向键…

Spring Boot 自动装配

本篇主要介绍Spring Boot 自动装配的相关内容。 目录 一、什么是自动装配 二、Bean的扫描方式 ComponentScan Import ImportSelector接口 三、Spring Boot自动装配原理 一、什么是自动装配 在我们在创建Spring Boot项目时往往会根据项目需求&#xff0c;引入很多第三方…

本地vite启动的vue项目使用nginx代理

前提&#xff1a; 必须在同一网段或者相同的局域网&#xff01;&#xff01;&#xff01; nginx下载通道&#xff1a; https://nginx.org/en/download.html 步骤&#xff1a; 1、最好下载稳定版本&#xff1a; 2、下载后直接解压&#xff08;注意&#xff1a;解压后不要放…

利用PS在不伤背景的前提下根据颜色去除图像上不想要的内容

下面为一个例子&#xff0c;去除图像上红色的虚线 Step1.用套索工具框选带有颜色的部分 Step2.切换到魔术棒工具&#xff0c;上端选项中&#xff0c;点击与选区交叉&#xff0c;连续这一项不要勾选 Step3.在需要去除的部分点击一下即可在框选范围内选中所有同颜色的区域&#x…

零基础学MySQL

1. 零基础学MySQL 1.1 数据库简介 1.1.1 数据库三层结构 1. 所谓安装Mysql数据库&#xff0c;就是在主机安装一个数据库管理系统(DBMS)&#xff0c;这个管理程序可以管理多个数据库。DBMS(database manage system) 2. 一个数据库中可以创建多个表,以保存数据(信息)。 3. 数据…

手写Windows文件路径获取小工具

手写Windows文件路径获取小工具 目的 给Windows右键增加功能&#xff0c;右键任何文件&#xff08;夹&#xff09;显示复制文件路径的扩展。 效果展示 实现思路 右键调用&#xff0c;自身会把文件路径传递给被调用文件&#xff0c;被调用文件内只需将路径参数复制到剪贴板即…

个人微信api

简要描述&#xff1a; 退出群聊 请求URL&#xff1a; http://域名地址/quitChatRoom 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/json Authorization&#xff1a;login接口返回 参数&#xff1a; 参数名 必选 类型 …

中国当代最具影响力的人物颜廷利:死神(死亡)并不可怕,可怕的是…

中国当代最具影响力的人物颜廷利&#xff1a;死神&#xff08;死亡&#xff09;并不可怕&#xff0c;可怕的是… 在中国优秀传统文化之中&#xff0c;汉语‘巳’字与‘四’同音&#xff0c;在阿拉伯数字里面&#xff0c;通常用‘4’来表示&#xff1b; 作为汉语‘九’字&#x…

docker(二):Centos安装docker

文章目录 1、安装docker2、启动docker3、验证 官方文档&#xff1a;https://docs.docker.com/engine/install/centos/ 1、安装docker 下载依赖包 yum -y install gcc yum -y install gcc-c yum install -y yum-utils设置仓库 yum-config-manager --add-repo http://mirrors…

KAN 笔记

1 Title KAN: Kolmogorov–Arnold Networks&#xff08;Ziming Liu, Yixuan Wang, Sachin Vaidya, Fabian Ruehle, James Halverson, Marin Soljačić, Thomas Y. Hou, Max Tegmark&#xff09;【2024】 2 Conclusion Inspired by the Kolmogorov-Arnold representat…

5.10.3 使用 Transformer 进行端到端对象检测(DETR)

框架的主要成分称为 DEtection TRansformer 或 DETR&#xff0c;是基于集合的全局损失&#xff0c;它通过二分匹配强制进行独特的预测&#xff0c;以及 Transformer 编码器-解码器架构。 DETR 会推理对象与全局图像上下文的关系&#xff0c;以直接并行输出最终的预测集。 1. …

【Linux】-Linux的实用操作:快捷键与软件安装操作、构建软连接、日期时区的设置[4]

目录 一、各类小技巧&#xff08;快捷键&#xff09; 1、ctrl c 强制停止 2、ctrl d 退出或登出 3、历史命令搜索 4、光标移动快捷键 5、清屏 二、软件安装 1、yum命令 2、apt命令 - 扩展&#xff08;ubuntu&#xff09; 三、systemctl命令 四、软连接 1、ln命令…

【字符函数与字符串函数】

文章目录 一、strlen函数1.strlen函数的使用2.strlen函数的模拟实现(1)计算器办法(2)不创建临时变量计数器(3)指针 二、strcpy函数1、strcpy函数的使用2、strcpy函数的模拟实现 三、strcat函数1、strcat函数的使用2、strcat模拟实现3、字符串自己给自己追加&#xff1f; 四、st…

01-单片机商业项目编程,从零搭建低功耗系统设计

一、引言 这是关于《单片机商业编程之从零搭建低功耗系统》的第一篇章&#xff0c;个人善忘&#xff0c;平常项目设计当中的一些思路&#xff0c;以前年轻的时候习惯性的录制成视频&#xff0c;也算是当作是自己的笔记&#xff0c;无奈现在喉咙实在扛不住&#xff0c;因此先尝试…

德克萨斯大学奥斯汀分校自然语言处理硕士课程汉化版(第一周) - 自然语言处理介绍和线性分类

自然语言处理介绍和线性分类 1. 自然语言处理介绍2. 线性二分类3. 情感分析和基础特征提取 3.1. 情感分析3.2. 特征提取3.3. 文本预处理 4. 学习的基础-梯度下降算法5. 感知机6. 逻辑回归7. 情感分析8. 感知机和逻辑回归 1. 自然语言处理介绍 自然语言处理的目标是什么 能够解…

(2024,KAN,MLP,可训练激活函数,样条函数,分层函数)Kolmogorov–Arnold 网络

KAN: Kolmogorov–Arnold Networks 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 简介 2. KAN 2.1 KA 表示定理 2.2 KAN 架构 2.3 KAN 的逼近能力和缩放定律 2.4 对于…