【高阶数据结构】AVL树
AVL树
- 1.AVL的概念
- 2.AVL树的实现
- 1.AVL树的结构
- 2.AVL树的插入
- 1.更新平衡因子
- 2.旋转
- 1.右单旋
- 2.左单旋
- 3.左右双旋
- 4.右左双旋
- 3.AVL树的查找
- 4.AVL树的平衡检测
- 5.AVL树的性能分析
- 6.AVL树的删除
- 3.总代码
- 1.AVLTree.h
- 2.Test.cpp
1.AVL的概念
- AVL树是最先发明的自平衡⼆叉查找树,AVL是⼀颗空树,或者具备下列性质的⼆叉搜索树:它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一颗高度平衡搜索二叉树, 通过控制高度差去控制平衡。
- AVL树得名于它的发明者G.M.Adelson-Velsky和E.M.Landis是两个前苏联的科学家,他们在1962年的论文《An algorithm for the organization of information》中发表了它。
- AVL树实现这里我们引入一个平衡因子(balance factor)的概念,每个结点都有一个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1,AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡,就像一个风向标一样。
- 思考一下为什么AVL树是高度平衡⼆叉搜索树,要求高度差不超过1,而不是高度差是0呢?0不是更好的平衡吗?画画图分析我们发现,不是不想这样设计,而是有些情况是做不到高度差是0的。比如一棵树是2个结点,4个结点等情况下,高度差最好就是1,无法做到高度差是0。
- AVL树整体结点数量和分布和完全⼆叉树类似,高度可以控制在logN,那么增删查改的效率也可以控制在O(logN),相比⼆叉搜索树有了本质的提升。

2.AVL树的实现
1.AVL树的结构
template<class k, class v>
struct AVLTreeNode
{ pair<k, v> _kv; //键值对//需要parent指针,后序更新平衡因子可以看到AVLTreeNode<k, v>* _left;AVLTreeNode<k, v>* _right;AVLTreeNode<k, v>* _parent;int _bf = 0; //平衡因子AVLTreeNode(const pair<k, v>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;//using Node = AVLTreeNode<K, V>;public://...private:Node* _root = nullptr;
};
2.AVL树的插入
- 插入一个值按⼆叉搜索树规则进行插入。
- 新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以需要更新从新增结点->根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可以停止了,具体情况我们下面再详细分析。
- 更新平衡因子过程中没有出现问题,则插入结束。
- 更新平衡因子过程中出现不平衡,对不平衡子树旋转,旋转后本质调平衡的同时,本质降低了子树的高度,不会再影响上一层,插入结束。
1.更新平衡因子
平衡因子更新原则:
- 平衡因子 = 右子树高度 - 左子树高度。
- 只有子树高度变化才会影响当前结点平衡因子。
- 插入结点,会增加高度,所以新增结点在parent的右子树,parent的平衡因子+1,新增结点在parent的左子树,parent平衡因子-1。
- parent所在子树的高度是否变化决定了是否会继续往上更新。
更新停止条件:
- 更新后parent的平衡因子等于0,更新中parent的平衡因子变化为 -1->0 或者 1->0,说明更新前parent子树一边高一边低,新增的结点插入在低的那边,插入后parent所在的子树高度不变,不会影响parent的父亲结点的平衡因子,更新结束。
- 更新后parent的平衡因子等于1或-1,更新前更新中parent的平衡因子变化为 0->1 或者 0->-1,说明更新前parent子树两边一样高,新增的插如结点后,parent所在的子树一边高一边低,parent所在的子树符合平衡要求,但是高度增加了1,会影响parent的父亲结点的平衡因高,所以要继续向上更新。
- 更新后parent的平衡因子等于2或-2,更新前更新中parent的平衡因子变化为 1->2 或者 -1->-2,说明更新前parent子树一边高一边低,新增的插入结点在高的那边,parent所在的子树高的那边更高了,破坏了平衡,parent所在的子树不符合平衡要求,需要旋转处理,旋转的目标有两个:1、把parent子树旋转平衡。2、降低parent子树的⾼度,恢复到插入结点以前的高度。所以旋转后也不需要继续往上更新,插入结束。
- 不断更新,更新到根,根的平衡因子是1或-1也停止了。
以下是三幅图的平衡因子更新过程:
- 更新到10结点,平衡因子为2,10所在的子树已经不平衡,需要旋转处理:
- 更新到中间结点,3为根的子树高度不变,不会影响上一层,更新结束:
- 最坏更新到根停止:
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;}else{parent->_left = cur;}cur->_parent = parent;//更新平衡因子while (parent){if (parent->_left == cur){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){//4种旋转情况if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}break;}else{assert(false);}}return true;
}
2.旋转
- 保持搜索树的规则。
- 让旋转的树重新变回平衡状态,其次降低旋转树的⾼度。
- 旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。
1.右单旋
- 下面的抽象图展示的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),a/b/c均符合AVL树的要求。10可能是整棵树的根,也可能是一个整棵树中局部的子树的根。这里a/b/c是高度为h的子树,是一种概括抽象表示,它代表了所有右单旋的场景,实际右单旋形态有很多种,具体下图进行了详细描述。
- 在a子树中插入一个新结点,导致a字树的高度从h变成h+1,不断向上更新平衡因子,导致10的平衡因子从-1变成-2,10为根的树左右高度差超过1,违反平衡规则。10为根的树左边太高了,需要往右边旋转,控制两棵树的平衡。
- 旋转核心步骤,因为5<b子树的值<10,将b变成10的左子树,10变成5的右子树,5变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的h+2,符合旋转原则。如果插入之前10整棵树的一个局部子树,旋转后不会再影响上一层,插入结束了。
下面给3幅图观察具体的旋转过程:




void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if(subLR)subLR->_parent = parent;//记录parent的父节点Node* pParent = parent->_parent; subL->_right = parent;parent->_parent = subL;//当parent是根节点时if (parent == _root){_root = subL;subL->_parent = nullptr;}else //当parent不是根节点时{subL->_parent = pParent;if (pParent->_left == parent)pParent->_left = subL;elsepParent->_right = subL;}//更新平衡因子subL->_bf = 0;parent->_bf = 0;
}
2.左单旋
- 下图展示的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),a/b/c均符合AVL树的要求。10可能是整棵树的根,也可能是一个整棵树中局部的子树的根。这里a/b/c是高度为h的子树,是一种概括抽象表示,它代表了所有右单旋的场景,实际右单旋形态有很多种,具体跟下面右单旋类似。
- 在a子树中插入一个新结点,导致a子树的高度从h变成h+1,不断向上更新平衡因子,导致10的平衡因子从1变成2,10为根的树左右高度差超过1,违反平衡规则。10为根的树右边太高了,需要往左边旋转,控制两棵树的平衡。
- 旋转核心步骤,因为10<b子树的值<15,将b变成10的右子树,10变成15的左子树,15变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的h+2,符合旋转原则。如果插入之前10整棵树的一个局部子树,旋转后不会再影响上一层,插入结束了。
下面给3幅图观察具体的旋转过程:
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if(subRL)subRL->_parent = parent;//记录parent的父节点Node* pParent = parent->_parent;subR->_left = parent;parent->_parent = subR;//当parent是根节点时if (parent == _root){_root = subR;subR->_parent = nullptr;}else //当parent不是根节点时{subR->_parent = pParent;if (pParent->_left == parent)pParent->_left = subR;elsepParent->_right = subR;}//更新平衡因子parent->_bf = 0;subR->_bf = 0;
}
3.左右双旋
- 先看一个例子:左边高时,如果插入的位置在5的右子树,以10为顶点采用右单旋无法解决问题,右单旋后,树依旧不平衡。如下图:
- 右单旋解决的纯粹的左边高,10为跟的子树不再是单纯的左边高,对于10是左边高,但是对于5是右边高,需要用两次旋转才能解决,先以5为旋转点进行一个左单旋,再以10为旋转点进行一个右单旋,之后这棵树就平衡了。如下图:
- 再来看一个例子:左边高时,如果插入的位置在8的左子树,以10为顶点采用右单旋无法解决问题,右单旋后,树依旧不平衡。
- 左右双旋解决:先以5为旋转点进行一个左单旋,再以10为旋转点进行一个右单旋,之后这棵树就平衡了。如下图:
- 最后看一个例子:左边高时,如果插入的位置在8的右子树,以10为顶点采用右单旋无法解决问题,右单旋后,树依旧不平衡。
- 左右双旋解决:先以5为旋转点进行一个左单旋,再以10为旋转点进行一个右单旋,之后这棵树就平衡了。如下图:
不同的场景,平衡因子的更新规则也不相同。如下三幅图:
- 场景一:5的右孩子8的平衡因子为0时:
- 场景二:5的右孩子8的平衡因子为-1时:
- 场景三:5的右孩子8的平衡因子为1时:
具体图转换为抽象图如下:
- 当h>=1时,新增结点插入在e子树,e子树高度从h-1变成h,并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为-1。旋转后8和5平衡因子为0,10平衡因子为1。
- 当h>=1时,新增结点插入在f子树,f子树高度从h-1变为h,并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为1。旋转后8和10平衡因子为0,5平衡因子为-1。
- 当h==0时,a/b/c都是空树,8自己就是一个新增结点,不断更新5->10平衡因子,引发旋转,其中8的平衡因子为0。旋转后8和10和5平衡因子均为0。

void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//更新平衡因子if (bf == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if(bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}
}
4.右左双旋
- 先看一个例子:右边高时,如果插入的位置在5的左子树,以10为顶点采用右单旋无法解决问题,右单旋后,树依旧不平衡。如下图:
- 左单旋解决的纯粹的右边高,5为跟的子树不再是单纯的右边高,对于5是右边高,但是对于10是左边高,需要用两次旋转才能解决,先以10为旋转点进行一个右单旋,再以5为旋转点进行一个左单旋,之后这棵树就平衡了。如下图:
- 再来看一个例子:右边高时,如果插入的位置在8的左子树,以5为顶点采用左单旋无法解决问题,左单旋后,树依旧不平衡。如下图:
- 右左双旋解决:先以10为旋转点进行一个右单旋,再以5为旋转点进行一个左单旋,之后这棵树就平衡了。如下图:
- 最后看一个例子:右边高时,如果插入的位置在8的右子树,以5为顶点采用左单旋无法解决问题,左单旋后,树依旧不平衡。如下图:
- 右左双旋解决:先以5为旋转点进行一个右单旋,再以10为旋转点进行一个左单旋,之后这棵树就平衡了。如下图:
不同的场景,平衡因子的更新规则也不相同。如下三幅图:
- 场景一:10的左孩子8的平衡因子为0时:
- 场景二:10的左孩子8的平衡因子为-1时:
- 场景三:10的左孩子8的平衡因子为1时:
具体图转换为抽象图如下:
- 当h>=1时,新增结点插入在e子树,e子树高度从h-1变为h,并不断更新12->15->10平衡因子,引发旋转,其中12的平衡因子为-1。旋转后10和12平衡因子为0,15平衡因子为1。
- 当h>=1时,新增结点插入在f子树,f子树高度从h-1变为h,并不断更新12->15->10平衡因子,引发旋转,其中12的平衡因子为1。旋转后15和12平衡因子为0,10平衡因子为-1。
- 当h==0时,a/b/c都是空树,12自己就是一个新增结点,不断更新15->10平衡因子,引发旋转,其中12的平衡因子为0,旋转后10和12和15平衡因子均为0。

void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);//更新平衡因子if (bf == -1){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 1;}else if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == 0){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 0;}else{assert(false);}
}
3.AVL树的查找
按照⼆叉搜索树逻辑实现即可,搜索效率为O(logN)
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;
}
4.AVL树的平衡检测
我们实现的AVL树是否合格,可以通过检查左右子树高度差的的程序进行反向验证,同时检查⼀下结点的平衡因子更新是否出现了问题。
public:int Height(){return _Height(_root);}bool IsAVLTree(){return _IsAVLTree(_root);}private:int _Height(Node* root){if (root == nullptr) return 0;int leftHelght = _Height(root->_left);int rightHelght = _Height(root->_right);return max(leftHelght, rightHelght) + 1;}bool _IsAVLTree(Node* root){//空树也是AVL树if (root == nullptr) return true;//计算当前节点的平衡因子:即高度差int leftHelght = _Height(root->_left);int rightHelght = _Height(root->_right);int bf = rightHelght - leftHelght;//如果计算出来的平衡因子的绝对值大于1,则一定不是AVL树if (abs(bf) > 2){cout << root->_kv.first << "高度差异常" << endl;return false;}//如果计算出来的平衡因子与root的平衡因子不相等时,则一定不是AVL树if (bf != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}//只有左右子树都是AVL树时:该树一定是AVL树return _IsAVLTree(root->_left) && _IsAVLTree(root->_right);}
void TestAVLTree1()
{AVLTree<int, int> t;//常规的测试用例 //int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//特殊的带有双旋场景的测试用例 int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}cout << t.IsAVLTree() << endl;
}
5.AVL树的性能分析
public:void InOrder(){_InOrder(_root);cout << endl;}int Size(){return _Size(_root);}private:void _InOrder(Node* root){if (root == nullptr) return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}int _Size(Node* root){if (root == nullptr) return 0;return _Size(root->_left) + _Size(root->_right) + 1;}
void TestAVLTree2()
{const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin1 = clock();AVLTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end1 = clock();cout << "Insert:" << end1 - begin1 << endl;cout << t.IsAVLTree() << endl;cout << t.Height() << endl;cout << t.Size() << endl;size_t begin2 = clock();//查找确定在的值 /*for (auto e : v){t.Find(e);}*///查找随机值for (size_t i = 0; i < N; i++){t.Find((rand() + i));}size_t end2 = clock();cout << "Find:" << end2 - begin2 << endl;
}
6.AVL树的删除
删除等待更新…
3.总代码
1.AVLTree.h
#pragma once#include<iostream>
#include<assert.h>
using namespace std;template<class k, class v>
struct AVLTreeNode
{ pair<k, v> _kv; //键值对//需要parent指针,后序更新平衡因子可以看到AVLTreeNode<k, v>* _left;AVLTreeNode<k, v>* _right;AVLTreeNode<k, v>* _parent;int _bf = 0; //平衡因子AVLTreeNode(const pair<k, v>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;//using Node = AVLTreeNode<K, V>;public: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;}else{parent->_left = cur;}cur->_parent = parent;//更新平衡因子while (parent){if (parent->_left == cur){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){//4种旋转情况if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}break;}else{assert(false);}}return true;}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;}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if(subLR)subLR->_parent = parent;//记录parent的父节点Node* pParent = parent->_parent; subL->_right = parent;parent->_parent = subL;//当parent是根节点时if (parent == _root){_root = subL;subL->_parent = nullptr;}else //当parent不是根节点时{subL->_parent = pParent;if (pParent->_left == parent)pParent->_left = subL;elsepParent->_right = subL;}//更新平衡因子subL->_bf = 0;parent->_bf = 0;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if(subRL)subRL->_parent = parent;//记录parent的父节点Node* pParent = parent->_parent;subR->_left = parent;parent->_parent = subR;//当parent是根节点时if (parent == _root){_root = subR;subR->_parent = nullptr;}else //当parent不是根节点时{subR->_parent = pParent;if (pParent->_left == parent)pParent->_left = subR;elsepParent->_right = subR;}//更新平衡因子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 == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if(bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}//右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);//更新平衡因子if (bf == -1){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 1;}else if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == 0){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 0;}else{assert(false);}}void InOrder(){_InOrder(_root);cout << endl;}int Height(){return _Height(_root);}bool IsAVLTree(){return _IsAVLTree(_root);}int Size(){return _Size(_root);}private:void _InOrder(Node* root){if (root == nullptr) return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}int _Height(Node* root){if (root == nullptr) return 0;int leftHelght = _Height(root->_left);int rightHelght = _Height(root->_right);return max(leftHelght, rightHelght) + 1;}bool _IsAVLTree(Node* root){//空树也是AVL树if (root == nullptr) return true;//计算当前节点的平衡因子:即高度差int leftHelght = _Height(root->_left);int rightHelght = _Height(root->_right);int bf = rightHelght - leftHelght;//如果计算出来的平衡因子的绝对值大于1,则一定不是AVL树if (abs(bf) > 2){cout << root->_kv.first << "高度差异常" << endl;return false;}//如果计算出来的平衡因子与root的平衡因子不相等时,则一定不是AVL树if (bf != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}//只有左右子树都是AVL树时:该树一定是AVL树return _IsAVLTree(root->_left) && _IsAVLTree(root->_right);}int _Size(Node* root){if (root == nullptr) return 0;return _Size(root->_left) + _Size(root->_right) + 1;}private:Node* _root = nullptr;
};
2.Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1#include<vector>#include"AVLTree.h"void TestAVLTree1()
{AVLTree<int, int> t;//常规的测试用例 //int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//特殊的带有双旋场景的测试用例 int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}t.InOrder();cout << t.IsAVLTree() << endl;
}void TestAVLTree2()
{const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin1 = clock();AVLTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end1 = clock();cout << "Insert:" << end1 - begin1 << endl;cout << t.IsAVLTree() << endl;cout << t.Height() << endl;cout << t.Size() << endl;size_t begin2 = clock();//查找确定在的值 /*for (auto e : v){t.Find(e);}*///查找随机值for (size_t i = 0; i < N; i++){t.Find((rand() + i));}size_t end2 = clock();cout << "Find:" << end2 - begin2 << endl;
}int main()
{//TestAVLTree1();//TestAVLTree2();return 0;
}
相关文章:
【高阶数据结构】AVL树
AVL树 1.AVL的概念2.AVL树的实现1.AVL树的结构2.AVL树的插入1.更新平衡因子2.旋转1.右单旋2.左单旋3.左右双旋4.右左双旋 3.AVL树的查找4.AVL树的平衡检测5.AVL树的性能分析6.AVL树的删除 3.总代码1.AVLTree.h2.Test.cpp 1.AVL的概念 AVL树是最先发明的自平衡⼆叉查找树&#…...
【Spring】基于XML的Spring容器配置——<bean>标签与属性解析
Spring框架是一个非常流行的应用程序框架,它通过控制反转(IoC)和依赖注入(DI)来简化企业级应用的开发。Spring容器是其核心部分,负责管理对象的创建、配置和生命周期。在Spring中,XML配置是一种…...
docker mysql5.7安装
一.更改 /etc/docker/daemon.json sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://do.nark.eu.org","https://dc.j8.work","https://docker.m.daocloud.io","https:/…...
HDR视频技术之十一:HEVCH.265 的 HDR 编码方案
前文我们对 HEVC 的 HDR 编码优化技术做了介绍,侧重编码性能的提升。 本章主要阐述 HEVC 中 HDR/WCG 相关的整体编码方案, 包括不同应用场景下的 HEVC 扩展编码技术。 1 背景 HDR 信号一般意味着使用更多比特,一般的 HDR 信号倾向于使用 10…...
最新的强大的文生视频模型Pyramid Flow 论文阅读及复现
《PYRAMIDAL FLOW MATCHING FOR EFFICIENT VIDEO GENERATIVE MODELING》 论文地址:2410.05954https://arxiv.org/pdf/2410.05954 项目地址: jy0205/Pyramid-Flow: 用于高效视频生成建模的金字塔流匹配代码https://github.com/jy0205/Pyram…...
Effective C++ 条款 11:在 `operator=` 中处理“自我赋值”
文章目录 条款 11:在 operator 中处理“自我赋值”核心问题示例:使用地址比较示例:copy-and-swap 技术设计建议总结 条款 11:在 operator 中处理“自我赋值” 核心问题 自我赋值风险 如果赋值操作符没有处理自我赋值(…...
19、鸿蒙学习——配置HDC命令 环境变量
一、下载Command Line Tools 可参考上篇《鸿蒙学习——配置OHPM、hvigor环境变量》 二、配置hdc环境变量 hdc命令行工具用于HarmonyOS应用/元服务调试所需的工具,该工具存放在命令行工具自带的sdk下的toolchains目录中。为方便使用hdc命令行工具,请将…...
初始 ShellJS:一个 Node.js 命令行工具集合
一. 前言 Node.js 丰富的生态能赋予我们更强的能力,对于前端工程师来说,使用 Node.js 来编写复杂的 npm script 具有明显的 2 个优势:首先,编写简单的工具脚本对前端工程师来说额外的学习成本很低甚至可以忽略不计,其…...
网络工程师常用软件之PING测试工具
老王说网络:网络资源共享汇总 https://docs.qq.com/sheet/DWXZiSGxiaVhxYU1F ☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝☝ 今天介绍一款好用的PING测试工具,ATKKPING。 ATKKPING的主要功能包括测试…...
深入探索仓颉编程语言:函数与结构类型的终极指南
引言 仓颉编程语言是一种现代化、语法精炼的编程语言,其设计目标是提供高度的灵活性与高性能的执行效率。函数与结构类型是仓颉语言的两大基础模块,也是开发者需要掌握的核心。本文将详细讲解仓颉语言中函数和结构类型的特性,辅以代码实例和…...
Java 对象的内存分配机制详解
在 Java 中,对象的内存分配是一个复杂但非常重要的过程。理解对象在堆中的分配方式,尤其是新生代和老年代的区别,对于优化 Java 应用程序的性能至关重要。本文将详细探讨 Java 对象在堆中的分配机制,包括新生代、老年代、Survivor…...
v8引擎垃圾回收
V8引擎垃圾回收机制 v8引擎负责JavaScript的执行。V8引擎具有内置的垃圾回收机制,用于自动管理内存分配和释放 堆与栈 栈空间 栈空间是小而连续的内存空间,主要用于存储局部变量和函数调用的相关信息,同时栈结构是“先进后出”的策略 栈…...
H5st5.0.0协议分析
签名核心:设备注册 5 8 9段签名校验 其中第八段主要收集了一些指纹信息 需要 对应一致 注册核心加密: fp localTk fp - 16位字符串 localTk - 92位字符串 tls指纹检测 py、js纯算皆可调用 注意:仅供学习交流,与作者无关&am…...
明达助力构建智能变电站新体系
背景概述 随着智能电网技术的飞速进步与电力需求的持续增长,变电站作为电力传输网络的核心节点,其运维效率及安全性能对电网的整体稳定运行起着决定性作用。传统的人工巡检和维护手段已难以匹配现代电网对高效性、实时性及智能化管理的迫切需求。因此&a…...
Flink优化----FlinkSQL 调优
目录 FlinkSQL 调优 1 设置空闲状态保留时间 2 开启 MiniBatch 3 开启 LocalGlobal 3.1 原理概述 3.2 提交案例:统计每天每个 mid 出现次数 3.3 提交案例:开启 miniBatch 和 LocalGlobal 4 开启 Split Distinct 4.1 原理概述 4.2 提交案例&…...
机器学习(二)-简单线性回归
文章目录 1. 简单线性回归理论2. python通过简单线性回归预测房价2.1 预测数据2.2导入标准库2.3 导入数据2.4 划分数据集2.5 导入线性回归模块2.6 对测试集进行预测2.7 计算均方误差 J2.8 计算参数 w0、w12.9 可视化训练集拟合结果2.10 可视化测试集拟合结果2.11 保存模型2.12 …...
01.01、判定字符是否唯一
01.01、[简单] 判定字符是否唯一 1、题目描述 实现一个算法,确定一个字符串 s 的所有字符是否全都不同。 在这一题中,我们的任务是判断一个字符串 s 中的所有字符是否全都不同。我们将讨论两种不同的方法来解决这个问题,并详细解释每种方法…...
第五届“传智杯”全国大学生计算机大赛(练习赛)水题题解
目录 复读 题目描述 输入格式 输出格式 输入输出 说明/提示 源代码 时钟 题目描述 输入格式 输出格式 输入输出 说明/提示 源代码 平等的交易 题目描述 输入格式 输出格式 输入输出 说明/提示 源代码 清洁工 题目描述 输入格式 输出格式 输入输出…...
iOS 苹果开发者账号: 查看和添加设备UUID 及设备数量
参考链接:苹果开发者账号下添加新设备UUID - 简书 如果要添加新设备到 Profiles 证书里: 1.登录开发者中心 Sign In - Apple 2.找到证书设置: Certificate,Identifiers&Profiles > Profiles > 选择对应证书 edit &g…...
推进数字园区建设-成都国际数字影像产业园
在当今数字化浪潮的席卷下,数字园区建设已成为推动区域经济发展、提升产业竞争力的关键举措。成都国际数字影像产业园作为数字产业领域的重要项目,以其独特的发展模式和创新实践,在推进数字园区建设方面取得了显著成效,为数字产业…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...





























