【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解
高阶数据结构 | 相关知识点 | 可以通过点击 | 以下链接进行学习 | 一起加油! |
---|---|---|---|---|
二叉搜索树 |
大家好,这里是店小二!今天我们将深入探讨高阶数据结构中的AVL树。AVL树是一种自平衡的二叉搜索树,可以看作是对传统二叉搜索树的优化版本。如果你对数据结构感兴趣,快拿出你的小本本,和我们一起开始这段探索之旅吧!
🌈个人主页:是店小二呀
🌈C语言专栏:C语言
🌈C++专栏: C++
🌈初阶数据结构专栏: 初阶数据结构
🌈高阶数据结构专栏: 高阶数据结构
🌈Linux专栏: Linux
🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅
文章目录
- 前文
- 一、AVL树概念
- 二、AVL树实现
- 2.1 AVL树节点的定义 (成员默认访问公有)
- 2.2 AVL树查找逻辑
- 2.3 AVL树插入逻辑
- 三、AVL的旋转(重点)
- 3.1 选用抽象图探讨AVL旋转
- 3.2 单旋问题
- 3.2.1 新节点插入较高左子树的左侧--左左:右单旋
- 3.2.2 新节点插入较高右子树的右侧--右右:左单旋
- 3.3 双旋解决问题
- 3.3.1 新节点插入较高左子树的右侧--左右:先左单旋再右单旋
- 3.3.2 根据结论修改AVL树节点平衡因子
- 3.3.3 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
- 3.3.4 小总结
- 四、AVL树的验证
- 4.1 验证其为二叉搜索树
- 4.2 验证其为平衡树
- 五、AVL树的删除(了解 )
- 六、AVL树的性能
- 七、AVLTree.h
前文
关于上篇二叉搜索树性能那块,探讨了二叉搜索树虽然可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家
G.M.Adelson-Velskii
和E.M.Landis
在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,按照这种规则形成的二叉搜索树为AVL树,保持着严格的平衡。
一、AVL树概念
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
- 平衡因子并不是必须,只是他的一种实现方式
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在logn
,搜索时间复杂度O(logn)
平衡因子计算:
- 平衡因子 = 右子树高度 - 左子树高度
二、AVL树实现
2.1 AVL树节点的定义 (成员默认访问公有)
template<class K, class V>struct AVLTreeNode{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf;//主要类型AVLTreeNode(const pair<K, V> & kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}};
AVL树每个结构存储一个pair<K, V>对象,其中需要parent指针目的是为了方便访问当前节点的上一个节点。
考虑到AVLTreeNode<K ,V>
书写麻烦,使用typedef进行类型重定义typedef AVLTreeNode<K, V> Node;
2.2 AVL树查找逻辑
Node* Find(const K& key){//按照搜索树Node* cur = _root;while (cur){if (key > cur->_kv.first){cur = cur->_right;}else if (key < cur->_kv.first){cur = cur->_left;}else{//返回当前节点的指针return cur;}}//防止意外发生。return nullptr;}
本质上来说AVL树属于二叉搜索树,关于cur->_kv.first
是cur通过指针去访问Node类中类型为pair<K, V>对象的_kv
。通过set与map简单学习。可以得知pair内部实现逻辑,_这里的kv.first
中first是K类型的对象。同时AVL也是二叉树搜索树,查找逻辑当然是使用二叉搜索树的逻辑。
2.3 AVL树插入逻辑
既然AVL也是二叉搜索树,那么插入逻辑也是一样的,不同在于需要进行平衡因子的调正。(平衡因子 = 右子树高度 - 左子树高度)
当插入节点结束后,对插入节点的父亲节点进行平衡因子的修改。当对于父亲节点达到特定值会对应不同的情况,不断利用parent向上调整父亲节点的平衡因子。
通过例图去分析,当父亲平衡因子到达特定值对应不同情况:
第一张:当parent平衡因子从1或-1变到0
第二张:这里出现两种情况,parent平衡因子从0变到1或-1,parent平衡因子从1或-1变到2或-2。只有从0变到1或-1,没有从2或-2变成1或-1,因为当parent平衡因子绝对值超过1时,已经出现问题,需要进行调正
具体说明:
- 按照搜索树规则插入
- 某个父亲节点的平衡因子
- 插入父亲的左孩子,父亲平衡因子–
- 插入父亲的右孩子,父亲平衡因子++
while (parent)
{//规矩平衡因子的规则,是根据特性和下面代码得来的if (parent->_left == cur){parent->_bf--;}else if (parent->_right == cur){parent->_bf++;}//判断是否回到父亲节点改动if (parent->_bf == 0){//没有啥问题,可以跳出程序break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}//出现问题,这种情况是由情况二变化过来的,那么就是说,cur和parent向上移动了else if (parent->_bf == 2 || parent->_bf == -2{//.............}
}
理解:
在节点插入后,需要进行平衡因子的更新。更新顺序是从下到上,结合着父亲节点平衡因子进行判断是否需要继续向上更新。
根据判断更新父亲的平衡因子状况进行处理(该平衡因子更新完后):
- 父亲平衡因子 == 0,父亲所在子树高度不变,不再继续往上更新,插入结束
- 父亲平衡因子 == 1 or -1, 父亲所在子树高度变了,继续往上更新上面节点的平衡因子
- 父亲平衡因子 == 2 or -2, 父亲所在的子树已经不平衡了,需要旋转处理
三、AVL的旋转(重点)
如果在一颗原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
这里可以将前面两种归为单旋进行处理,而后面两种需要通过双旋进行处理。
提前说明:一个AVL树有多个AVL树或者说一个复杂的AVL树是由许多个简单的AVL树进行组合而成的。那么如果插入一个节点导致AVL树失去平衡,这里AVL树指以插入节点的父亲节点为根节点的子AVL树。遵守一个有问题解决问题,如果只是这一课AVL树有问题,就解决这颗AVL树,正常的AVL树我们是不要去过多的进行处理。
根据说明,如果将一整棵AVL树全部拆分进行研究,不同节点插入的位置很多选择,情况有很多种,难以全部罗列出来,而且没有必要,如果将一颗有问题AVL树治好,就可以将任何一颗AVL树治好,所以这边采用使用抽象图进行分析。
3.1 选用抽象图探讨AVL旋转
其中使用a、b、c子AVL树及其高度设置为h
抽象图中的抽象的AVL树平衡因子这里不用看的,目前罗列的情况是可以包含这里的,这里小部分就是大部分调正平衡因子的过程,意味着这部分也可以是抽象AVL树中的情况按照当前处理已经进行了调整。
3.2 单旋问题
使用单旋场景:parent->_bf == 2 && cur->_bf == 1
或者parent->_bf == -2 && cur->_bf == -1
3.2.1 新节点插入较高左子树的左侧–左左:右单旋
场景:parent->_ bf == -2 && cur->_bf == -1
具体解析旋转步骤:
目前AVL树是平衡的,当插入新节点30到左子树中,左子树高度加一层,导致以60为根的子AVL树失去平衡。为了使得平衡,意味着节点60的右子树也需要增加一层,那么将60节点成为30节点的左孩子,同时原本30节点的左孩子和60节点成为30节点的左孩子的高度相同,那么将原本30节点的左孩子成为60节点有孩子。这里保证了30节点原本左孩子一定是小于60节点的。更新节点的平衡因子即可,简单概况就是30 < b子树 < 60 < c子树
旋转过程中,有以下几点情况需要考虑:
- 30节点的右孩子可能存在,也可能不存在
- 60可能是根节点,也可能是子树
- 如果是根节点,旋转完成后,要更新根节点
- 如果是子树,可能是某个节点的左子树,也可能是右子树
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 (parent == _root){SubL = _root;SubL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubL;}else if(ppNode->_right == parent){ppNode->_right = SubL;}SubL->_parant = ppNode;}SubL->_bf = 0;parent->_bf = 0;
}
通过旋转使得不平衡AVL树得到调整,这里不需要考虑向上更新平衡因子。从抽象图中可以得出旋转后结论,直接使用结论修改平衡因子就行。
3.2.2 新节点插入较高右子树的右侧–右右:左单旋
场景:parent->_ bf == 2 && cur->_bf == 1
这里和右单旋是差不多的,具体流程可以去看上述和代码分析
void RotateL(Node* parent)
{Node* SubR = parent->_right;Node* SubRL = SubR->_left;parent->_right = SubRL;if (SubRL)SubR->_parent = parent;SubR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){SubR = _root;SubR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubR;}else if (ppNode->_right == parent){ppNode->_right = SubR;}SubR->_parant = ppNode;}SubR->_bf = 0;parent->_bf = 0;
}
3.3 双旋解决问题
如果出现了这种if (parent->_bf == 2 || cur->_bf == -1)
, if (parent->_bf == -2 || cur->_bf == 1)
普通单旋是不能解决问题的,需要使用到双旋。
如果使用单旋的话是没有多大作用的,做无用功。我们使用单旋处理的情况是一边独高,而不是那边高,这边高。对此可以先对内部旋转变成单独一边高,再使用单旋进行调整AVL树。
3.3.1 新节点插入较高左子树的右侧–左右:先左单旋再右单旋
场景:parent->_bf == -2 && cur->_bf == 1
这里可以直接复用实现好单旋的接口,但是需要对平衡因子进行调整,这里平衡因子可以通过结论直接修改。
3.3.2 根据结论修改AVL树节点平衡因子
通过图中对于不同情况的分析,对于修改平衡因子的结果是根据SubLR平衡因子特殊值决定的。b、c分别给谁,会影响平衡因子的分配。
3.3.3 新节点插入较高右子树的左侧—右左:先右单旋再左单旋
场景:parent->_bf == 2 && cur->_bf == -1
这里还是需要根据60这块的平衡因子去判断的,b c分别给谁,会影响平衡因子的分配。左边新增两次旋转给到右边,右边新增两次旋转给到左边,通过结论最后修改平衡因子。
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else{parent->_bf = 0;subR->_bf = 0;}}void RotateLR(Node* parent)
{Node* SubL = parent->_left;Node* SubLR = SubL->_right;int _bf = SubLR->_bf;RotateL(parent->_left);RotateR(parent);if (_bf == 0){parent->_bf = 0;SubL->_bf = 0;SubLR->_bf = 0;}else if (_bf == 1){parent->_bf = 0;SubL->_bf = -1;SubLR->_bf = 0;}else if (_bf == -1){parent->_bf = 1;SubL->_bf = 0;SubLR->_bf = 0;}else{assert(false);}
}
3.3.4 小总结
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑 :
- pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
- 当pSubR的平衡因子为1时,执行左单旋
- 当pSubR的平衡因子为-1时,执行右左双旋
- pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
- 当pSubL的平衡因子为-1是,执行右单旋
- 当pSubL的平衡因子为1时,执行左右双旋
旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新
四、AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分为两步:验证是否为二叉搜索树,验证是否为平衡树
4.1 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
void InOder()
{_InOder(_root);cout << endl;
}private:
void _InOder(Node* _root)
{if (_root == nullptr){return;}InOder(_root->_left);cout << _root->_kv.first << " " << _root->_kv.second << endl;InOder(_root->_right);
}
这里将中序遍历实现逻辑封装到私有成员函数中隐藏它的具体实现细节,使得外部用户无法直接访问该函数,提高了代码的安全性和可维护性,符合面向对象编程的封装性原则。这里外部访问中序遍历接口,只是传递了节点指针的副本,而不修改任何节指针的实际值。
4.2 验证其为平衡树
规则:
- 每个节点子树高度差的绝对值不超过1(注意节点种种那个如果没有平衡因子)
- 节点的平衡因子是否计算正确
bool IsBalance()
{return _IsBalance(_root);
}int Height()
{return _Height(_root);
}int Size()
{return _Size(_root);
}private:
int _Size(Node* root)
{return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}int _Height(Node* root)
{if (root == nullptr)return 0;return max(_Height(root->_left), _Height(root->_right)) + 1;
}bool _IsBalance(Node* root)
{if (root == nullptr)return true;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);// 不平衡if (abs(leftHeight - rightHeight) >= 2){cout << root->_kv.first << endl;return false;}// 顺便检查一下平衡因子是否正确if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << endl;return false;}return _IsBalance(root->_left)&& _IsBalance(root->_right);
}void _InOder(Node* _root)
{if (_root == nullptr){return;}InOder(_root->_left);cout << _root->_kv.first << " " << _root->_kv.second << endl;InOder(_root->_right);
}
五、AVL树的删除(了解 )
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置 ,这里调正平衡因子的逻辑就需要反过来,同时参考下二叉搜索树的删除操作。
六、AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即O(log^n^
)`。但是如果要对AVL树做一些结构修改的操作,性能非常低下。
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合
补充调式小技巧:
void TestAVLTree1()
{//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };AVLTree<int, int> t1;for (auto e : a){/*if (e == 4){int i = 0;}*/// 1、先看是插入谁导致出现的问题// 2、打条件断点,画出插入前的树// 3、单步跟踪,对比图一一分析细节原因t1.Insert({e,e});cout <<"Insert:"<< e<<"->"<< t1.IsBalance() << endl;}t1.InOrder();cout << t1.IsBalance() << endl;
}
七、AVLTree.h
#pragma once
#include <assert.h>
#include <iostream>
using namespace std;//设置默认权限为公用的结构体
template<class K, class V>struct AVLTreeNode{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf;//主要类型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:Node* Find(const K& key){//按照搜索树Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{//返回当前节点的指针return cur;}}return nullptr;}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}//也是查找需要插入的地方,进行插入Node* parent = nullptr;Node* cur = _root;//目的就是让cur走到合适的空位置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{assert(!cur);}}//需要将这个节点连接起来cur = new Node(kv);cur->_parent = parent;if (parent->kv.first > cur->_kv.first){parent->_left = cur;}else if (parent->kv.first < cur->_kv.first){parent->_right = cur;}//上述完成了插入的逻辑,调正平衡因子while (parent){//平衡因子的规则if (parent->_left == cur){parent->_bf--;}else if (parent->_right == cur){parent->_bf++;}//判断是否回到父亲节点改动if (parent->_bf == 0){//没有啥问题,可以跳出程序break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}//出现问题,这种情况是由情况二变化过来的,那么就是说,cur和parent向上移动了else if (parent->_bf == 2 || parent->_bf == -2){//这里根据规律,去固定改变平衡因子if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//双旋if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else{assert(false);}}}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 (parent == _root){SubL = _root;SubL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubL;}else if (ppNode->_right == parent){ppNode->_right = SubL;}SubL->_parant = ppNode;}SubL->_bf = 0;parent->_bf = 0;}void RotateL(Node* parent){Node* SubR = parent->_right;Node* SubRL = SubR->_left;parent->_right = SubRL;if (SubRL)SubR->_parent = parent;SubR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = SubR;if (parent == _root){SubR = _root;SubR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = SubR;}else if (ppNode->_right == parent){ppNode->_right = SubR;}SubR->_parant = ppNode;}SubR->_bf = 0;parent->_bf = 0;}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;}else{parent->_bf = 0;subR->_bf = 0;}}void RotateLR(Node* parent){Node* SubL = parent->_left;Node* SubLR = SubL->_right;int _bf = SubLR->_bf;RotateL(parent->_left);RotateR(parent);if (_bf == 0){parent->_bf = 0;SubL->_bf = 0;SubLR->_bf = 0;}else if (_bf == 1){parent->_bf = 0;SubL->_bf = -1;SubLR->_bf = 0;}else if (_bf == -1){parent->_bf = 1;SubL->_bf = 0;SubLR->_bf = 0;}else{assert(false);}}void InOder(){_InOder(_root);cout << endl;}bool IsBalance(){return _IsBalance(_root);}int Height(){return _Height(_root);}int Size(){return _Size(_root);}private:int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}int _Height(Node* root){if (root == nullptr)return 0;return max(_Height(root->_left), _Height(root->_right)) + 1;}bool _IsBalance(Node* root){if (root == nullptr)return true;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);// 不平衡if (abs(leftHeight - rightHeight) >= 2){cout << root->_kv.first << endl;return false;}// 顺便检查一下平衡因子是否正确if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << endl;return false;}return _IsBalance(root->_left)&& _IsBalance(root->_right);}void _InOder(Node* _root){if (_root == nullptr){return;}InOder(_root->_left);cout << _root->_kv.first << " " << _root->_kv.second << endl;InOder(_root->_right);}private:Node* _root = nullptr;};void AVLTest1()
{AVLTree<int, int> at;
}
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是高阶数据结构笔记,希望对你在学习高阶数据结构旅途中有所帮助!
相关文章:

【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解
高阶数据结构相关知识点可以通过点击以下链接进行学习一起加油!二叉搜索树 大家好,这里是店小二!今天我们将深入探讨高阶数据结构中的AVL树。AVL树是一种自平衡的二叉搜索树,可以看作是对传统二叉搜索树的优化版本。如果你对数据结…...

中级软考软件设计师真题+模拟题+课件讲解+机考讲解模拟+笔记分享
软考真题分享 下载链接⬇️⬇️: 下载链接...

MySQL—视图
前言: 视图是一个虚拟的表,是基于一个或多个基本表或其他视图的查询结果集。视图本身不占据物理储存空间,仅仅只是一个查询的逻辑表示,物理上依赖于数据表的数据。 视图具有简单,安全,逻辑数据独立&#…...

鸿蒙OS启动流程
启动流程(基于openharmony4.1) 系统上电加载内核后,按照以下流程完成系统各个服务和应用的启动: 内核加载init进程,一般在bootloader启动内核时通过设置内核的cmdline来指定init的位置。init进程启动后,会挂载tmpfs,…...

服务器数据恢复—EMC存储RAID5磁盘阵列数据恢复案例
服务器数据恢复环境: 一台EMC某型号存储设备,该存储中有一组由12块(包括2块热备盘)STAT硬盘组建的raid5阵列。 服务器故障: 该存储在运行过程中突然崩溃,raid瘫痪。数据恢复工程师到达现场对故障存储设备进…...
使用 `netcat`(nc)工具进行TCP数据发送和接收
Netcat(通常缩写为nc)是一个功能强大的网络工具,常被称为“瑞士军刀”的网络工具。它用于在网络上进行各种操作,比如读写网络连接、调试和分析网络服务等。以下是关于Netcat的一些详细介绍: ### 主要功能 1. **TCP/U…...
Linux虚拟化技术嬗变综述
在信息技术飞速发展的今天,虚拟化技术已经成为数据中心、云计算和企业IT基础设施中不可或缺的一部分。Linux操作系统作为开源和灵活的代表,在虚拟化技术的发展中扮演了重要角色。本文将综述Linux虚拟化技术的演变,探讨其优势、应用场景&#…...

.NET 通过C#设置Excel工作表的页面设置
Excel文件数据准备就绪并需要以报告形式呈现时,调整Excel文件的页面设置变得尤为重要,不仅关乎文档的专业外观,还直接影响到打印或电子分享时的可读性和实用性。通过C#来自动化这一过程,不仅可以节省大量手动配置的时间࿰…...

Excel日期导入数据库变为数字怎么办
在Excel导入到数据库的过程中,经常会碰到Excel里面的日期数据,导进去过后变成了数字。 如下图: 使用navicate等数据库编辑器导入数据库后: 原因分析:这是因为日期和时间在excel中都是以数字形式存储的,这个…...

SSL---SSL certificate problem
0 Preface/Foreword 0.1 SSL certificate problem 开发过程中,gitlab-runner连接gitlab时候出现SSL 证书问题。 场景:公司的gitlab runner服务器引入了SSL证书,每年都会主动更新一次。当前的gitlab-runner运行在PC机器上,但是g…...

linux tar 打包文件去掉文件所在路径
一、准备目录 /root/tmp/images /root/tmp/images2 执行命令打包目录/root/tmp/images 到 /root/tmp/images.tar.gz 再解压到/root/tmp/images2 cd /root/tmp/images && tar -cvzf images.tar.gz * && mv images.tar.gz /root/tmp/ tar -C /root/tmp/image…...

MQ快速入门【详细】个人笔记 讲解通俗易懂
1.同步通讯和异步通讯 同步通讯:如果举个例子来说,同步通讯就像是两个人在打电话,一方说的话,能够立马传给另一方,消息的时效性非常高,但是相对的,只能是给一个人通讯,如果这个时候&…...

react实现实时计时的最简方式
js中时间的处理,不借助于moment/dayjs这样的工具库,原生获取格式化的时间,最简单的实现方式可以参考下面这样。 实现效果 代码实现 封装hooks import { useState, useEffect } from "react";export function useCountTime() {c…...
时尚的社会心理机制:求同和树异这对互为矛盾的心理动机,使得人们在社会生活中互相模仿、互相追逐、互相竞争,使得时尚的钟摆永不停息。
文章目录 引言I 时尚时尚的社会心理机制时尚的分类时尚的特点时尚的表现形式II 术语时装周服饰引言 时尚(fad)又称流行,它指在一定时期内社会上或一个群体中普遍流行的,并为大多数所仿效的生活方式或行为模式。 人的心理动机常常是互相矛盾的,既要求同于人,又要求异于人。…...

HarmonyOS NEXT应用开发实战(二、封装比UniApp和小程序更简单好用的网络库)
网络访问接口,使用频次最高。之前习惯了uniapp下的网络接口风格,使用起来贼简单方便。转战到鸿蒙上后,原始网络接口写着真累啊!目标让鸿蒙上网络接口使用,简单程度比肩uniapp,比Axios更轻量级。源码量也不多…...

[Hbase]一 HBase基础
1. HBase简介 1.1 HBase定义 HBase数据模型的关键在于 稀疏、分布式、多维、排序 的映射。其中映射 map指代非关系型数据库的 key-Value结构。 1.2 HBase数据模型 1)Name Space 命名空间,类似于关系型数据库的database 概念,每个命名空间下有多个表。HBase 两个自…...

React.createRef(),React.forwardRef(),forwardRef()结合next.js的link进行路由跳转
码云https://gitee.com/skyvilm/react-next.js 1.React.createRef() 作用:获取dom元素 使用 import React,{Component} from react export default class Index extends Componen{ constructor(props){ super(props) this.myrefReact.createRef(); //创建节点 } c…...

C++从入门到起飞之——AVL树 全方位剖析!
🌈个人主页:秋风起,再归来~🔥系列专栏:C从入门到起飞 🔖克心守己,律己则安 目录 1. AVL的概念 2. AVL树的实现 2.1 AVL树的结构 2.2 AVL树的插⼊ >AVL树插⼊⼀个值的⼤概过程 &…...
利用Fail2Ban增强Jupyter Notebook安全性以防范目录遍历攻击
利用Fail2Ban增强Jupyter Notebook安全性以防范目录遍历攻击 书接上回[^参见]一、目录遍历攻击(Directory Traversal Attack)二、日志记录的网络攻击示例分析三、配置 Fail2ban四、fail2ban-regex测试和验证正则表达式五、重启 Fail2Ban六、验证配置生效…...

智能贴身监测,健康生活建议,圆道妙医智能手表体验
如今热衷于运动和健康生活的爱好者越来越多,相关的赛事等活动也是逐年增多,很多朋友为了能够直观的了解自己的健康状况,都会配备一款智能手表,这样戴在身上就可以随时了解自己的心率、血氧等数据。最近我尝试了一款圆道妙医推出的…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...

毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...

Visual Studio Code 扩展
Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后,命令 changeCase.commands 可预览转换效果 EmmyLua…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...

【iOS】 Block再学习
iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...