文章目录

  • 一、AVL树的概念
  • 二、AVL树的实现
    • AVL树的结构
    • AVL树的插⼊
      • AVL树插⼊⼀个值的⼤概过程
      • 平衡因⼦更新
        • 更新原则
        • 更新停⽌条件
        • 插入结点及更新平衡因子的代码实现
      • 旋转
        • 旋转的原则
        • 右单旋
          • 右单旋代码实现
        • 左单旋
          • 左单旋代码实现
        • 左右双旋
        • 左右双旋代码实现
        • 右左双旋代码实现
        • 判断旋转
    • 中序遍历
    • 平衡检测(求高度)
    • 查找
    • Size(求结点个数)
  • 三、源码


一、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),相⽐⼆叉搜索树有了本质的提升。

二、AVL树的实现

AVL树的结构

template<class K, class V>
struct AVLTreeNode
{// 需要parent指针,后续更新平衡因⼦可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(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;
public://...
private:Node* _root = nullptr;
};

结点的成员变量这一块我们不再分别定义key和value,而是用pair包起来,和map保持一致,方便后续模拟实现myset和mymap。相比以前多了一个指向parent的指针,后面的平衡旋转操作会用到。还多了一个平衡因子_bf。
至于AVL树的定义基本和之前的二叉搜索树一样。

AVL树的插⼊

AVL树插⼊⼀个值的⼤概过程

  1. 插入:插⼊⼀个值按⼆叉搜索树规则进⾏插⼊。
  2. 更新平衡因⼦:新增结点以后,只会影响该结点祖先结点的⾼度,也就是可能会影响部分祖先结点的平衡因⼦,所以更新从新增结点->根结点路径上的平衡因⼦,实际中最坏情况下要更新到根,有些情况更新到中间就可以停⽌了,具体情况我们下⾯再详细分析。

更新平衡因⼦过程中没有出现不平衡,则插⼊结束。
更新平衡因⼦过程中出现不平衡,对不平衡⼦树旋转,旋转后调平衡的同时,本质降低了⼦树的⾼度,不会再影响上⼀层,所以插⼊结束。

平衡因⼦更新

更新原则
  • 平衡因⼦ = 右⼦树⾼度-左⼦树⾼度。
  • 只有⼦树⾼度变化才会影响当前结点平衡因⼦。
  • 插⼊结点,会增加当前结点parent的子树⾼度,如果新增结点在parent的右⼦树,parent的平衡因⼦++,新增结点在parent的左⼦树,parent平衡因⼦–。
  • 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也停⽌更新。
插入结点及更新平衡因子的代码实现

插入操作和之前二叉搜索树的逻辑相同,重点是这里更新平衡因子的代码实现。 我们前面介绍过了更新平衡因子有三种可能(0, 1-1, 2-2),但是我们在代码里最好不要就只分三种情况讨论,因为这三种情况是建立在前面代码逻辑都正常的情况下,但是代码是有可能出错的,所以我们需要在代码里再多写一个出现错误的情况,如果走到这种情况了那么程序一定出现了问题,我们可以assert或者抛异常。
更行结束标志也有三种,一种是更新到根节点,parent指向nullptr,停止更新。一种是parent平衡因子更新到0,停止更新。还有一种是parent平衡因子更新到2或者-2,不平衡了需要旋转,旋转完后因为高度复原了所以停止更新。

bool Insert(const pair<K, V>& kv)
{//单纯插入if (_root == nullptr){_root = new Node(kv);return true;}else{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){parent = cur;cur = cur->_right;}else{return false;}}cur = Node(kv);if (parent->_kv.first > kv.first)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;}//更新平衡因子while (parent) //若更新到根结点,更新结束---1{if (parent->_left == cur)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0){//更新结束---2break;}else if (parent->_bf == 1 || parent->_bf == -1){//继续向上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//不平衡了,旋转处理//旋转完后,更新结束---3break;}else{//代码出错assert(false);}}return true;

旋转

旋转的原则

1、旋转后保持搜索树的规则
2. 让旋转的树从不满⾜变平衡,其次降低旋转树的⾼度。
3. 旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。
说明:下⾯的图中,有些结点我们给的是具体值,如10和5等结点,这⾥是为了⽅便讲解,实际中是什么值都可以,只要⼤⼩关系符合搜索树的性质即可。

右单旋
  • 图中展⽰的是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是整棵树的⼀个局部⼦树,旋转后局部⼦树的高度恢复到插入之前,不会再影响上⼀层,插⼊结束。
    这里要把a打包看成一个整体,如果插入a也会引起旋转那么a旋转的步骤也和我们这里讨论的一样,所以这里我们只是讲的是一个旋转的模板,不管是a子树里面的旋转还是在10上面的树的旋转都适用。(如果10不是根节点的话)

在这里插入图片描述

右单旋代码实现

一、首先把parent->_left = subLR, subL->_right = parent,这两个调整孩子的步骤很简单。
二、但是我们要记住AVL树是三叉链,它的每个结点还有一个指向parent的指针,所以还需要调整subLR、parent、subL的_parent指针,调整_parent比较复杂。

  • subLR->_parent = parent: 这里要注意subLR是可能为空的,所以需要判断一下,为空就无需调整subLR的_parent。
  • parent->_parent = subL: 这里没有什么问题。
  • subL->_parent 这里要分两种情况讨论,一种是当parent是根节点,那么需要把_root给给subL,subL->_parent = nullptr。一种是parent不是根节点,那么parent只是其中一颗子树的根节点,所以需要维护我们在旋转子树与整个树的关系,需要把subL->parent指向parent->parent,然后把parent->parent的_left或者_right指向subL,具体细节看下面代码的注释。

三、最后调整平衡因子,旋转过程只有parent和subL的平衡因子会改变,而且都是变为0。

void RotateR(Node* parent)
{//调整两个结点的孩子指针Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;//调整三个结点的父亲指针if (subLR) // 结点1subLR->_parent = parent;//提前保存一下parent->_parentNode* parentParent = parent->_parent;parent->_parent = subL; // 结点2//subL的_parent分三种情况if (parent == _root){//parent是根节点_root = subL;subL->_parent = nullptr;}else{subL->_parent = parentParent;if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL; // 结点3}}//调整平衡因子subL->_bf = parent->_bf = 0;
}
左单旋

左单旋和右单旋逻辑上基本一致,小编这里就不细讲了。

在这里插入图片描述

左单旋代码实现
void RotateL(Node* parent)
{//调整两个结点的孩子指针Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;//调整三个结点的父亲指针if (subRL) // 结点1subRL->_parent = parent;//提前保存一下parent->_parentNode* parentParent = parent->_parent;parent->_parent = subR; // 结点2//subR的parent分三种情况if (parent == _root){//parent是根节点_root = subR;subR->_parent = nullptr;}else{subR->_parent = parentParent;if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR; // 结点3}}//调整平衡因子subR->_bf = parent->_bf = 0;
}
左右双旋

通过下面两幅图可以看到,左边⾼时,如果插⼊位置不是在a⼦树,⽽是插⼊在b⼦树,b⼦树⾼度从h变成h+1,引发旋转,右单旋⽆法解决问题,右单旋后,我们的树依旧不平衡。右单旋解决的纯粹的左边⾼,但是插⼊在b⼦树中,10为跟的⼦树不再是单纯的左边⾼,对于10是左边⾼,但是对于5是右边⾼,需要⽤两次旋转才能解决。以5为旋转点进⾏⼀个左单旋,(把一开始根的左边小的右边小调整成一边小,这里被调整后都是左边小)以10为旋转点进⾏⼀个右单旋,这棵树就平衡了。(旋转前是一边小再单旋就可以平衡)

在这里插入图片描述
在这里插入图片描述

以5为旋转点进⾏⼀个左单旋,以10为旋转点进⾏⼀个右单旋,这棵树这棵树就平衡了。

在这里插入图片描述

实际上左右双旋是可以一步到位的,相当于把subLR的左边分给了subL的右边,subLR的右边分给了parent的左边,最后把subLR推上去做根,我们看下面这幅图:

在这里插入图片描述

但是左右双旋体现在代码上还是一步一步来,因为可以复用单旋的代码,更爽一点。

平衡因子的更新:
左右双旋的旋转过程如上所示,只有一种情况,但是三个结点平衡因子的更新有三种情况,分别是将结点插入到b子树的e子树(情况一)、b子树的f子树(情况二)、b子树本身为空(情况三),插入的结点本身就成了b子树。插入时的前两种情况旋转结束后体现在subL右子树的高度和parent左子树的高度,最后一种情况表明旋转只有三个结点参与,三个结点都没有子树。
最后平衡因子的结果是subLR始终为0,情况一subL为0,parent为-1,情况二subL为-1,parent为0,情况三subL、parent都为0。
要判别平衡因子的更新是哪种情况就需要记录插入后subLR的平衡因子,而且需要在两次单旋之前把值记录下来,因为单旋会把结点的平衡因子都置为0,(因为单旋是以只有一边大的逻辑来旋转和调整平衡因子的,所以左右双旋复用单旋代码后需要手动调整平衡因子)如果subLR的平衡因子为-1那么是情况1,为1那么是情况2,为0那么是情况3。

左右双旋代码实现
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int tembf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//更新平衡因子subLR->_bf = 0;if (tembf == -1)     //情况一{subL->_bf = 0;parent->_bf = 1;}else if(tembf == 1)  //情况二{subL->_bf = -1;parent->_bf = 0;}else if(tembf == 0)  //情况三{subL->_bf = subLR->_bf = 0;}else{assert(false);}
}
右左双旋代码实现
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int tembf = subRL->_bf;RotateR(subR);RotateL(parent);//更新平衡因子subRL->_bf = 0;if (tembf == -1)      //情况一{subR->_bf = 1;parent->_bf = 0;}else if(tembf == 1)   //情况二{subR->_bf = 0;parent->_bf = -1;}else if (tembf == 0)  //情况三{subR->_bf = subRL->_bf = 0;}else{assert(false);}
}
判断旋转
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);
}

我们可以总结发现,单旋时两个结点的平衡因子同号,双旋时两个结点的平衡因子异号。

中序遍历

void InOrder()
{_InOrder(_root);cout << endl;
}void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);
}

平衡检测(求高度)

我们实现的AVL树是否合格,就是检查每一个结点的平衡因子是否平衡,但是不能只检查平衡因子,因为平衡因子也可能出问题,所以需要递归求出每一个结点的平衡因子是否符合要求,然后再拿算的平衡因子和结点内部的平衡因子比较。方法就是通过递归求当前结点左右子树的高度,然后求当前结点左右⼦树⾼度差进⾏反向验证。
(代码中的abs 是 C++ 标准库中的一个函数,用于计算整数的绝对值(即去掉符号,只保留数值大小)

int Height()
{return _Height(_root);
}bool IsBalanceTree()
{return _IsBalanceTree(_root);
} int _Height(Node* root)
{if (root == nullptr)return 0;int LHeight = _Height(root->_left);int RHeight = _Height(root->_right);return 1 + (LHeight > RHeight ? LHeight : RHeight);
}bool _IsBalanceTree(Node* root)
{if (root == nullptr)return true;   //空树也是AVL树int diff = _Height(root->_right) - _Height(root->_left);if (abs(diff) >= 2){cout << "高度差异常" << endl;return false;}else if (diff != root->_bf){cout << "平衡因子异常" << endl;return false;}return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

查找

bool Find(const K& x)
{Node* cur = _root;while (cur){if (cur->_kv.first == x)return true;else if (cur->_kv.first > x)cur = cur->_left;elsecur = cur->_right;}return false;
}

Size(求结点个数)

int Size()
{return _Sise(_root);
}int _Sise(Node* _root)
{if (_root == nullptr)return 0;return 1 + _Sise(_root->_left) + _Sise(_root->_right);
}

三、源码

AVLTree.h:

#pragma once
using namespace std;
#include <iostream>
#include <assert.h>
#include <vector>template<class K, class V>
struct AVLTreeNode
{// 需要parent指针,后续更新平衡因⼦可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(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;
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->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first) //判断cur插在parent的那边parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//更新平衡因子while (parent) //若更新到根结点,更新结束---1{if (parent->_left == cur)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0){//更新结束---2break;}else if (parent->_bf == 1 || parent->_bf == -1){//继续向上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//不平衡了,旋转处理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);}//旋转完后,更新结束---3break;}else{//代码出错assert(false);}}return true; }void InOrder(){_InOrder(_root);cout << endl;}int Height(){return _Height(_root);}bool IsBalanceTree(){return _IsBalanceTree(_root);}  bool Find(const K& x){Node* cur = _root;while (cur){if (cur->_kv.first == x)return true;else if (cur->_kv.first > x)cur = cur->_left;elsecur = cur->_right;}return false;}int _Height(Node* root){if (root == nullptr)return 0;int LHeight = _Height(root->_left);int RHeight = _Height(root->_right);return 1 + (LHeight > RHeight ? LHeight : RHeight);}bool _IsBalanceTree(Node* root){if (root == nullptr)return true;   //空树也是AVL树int diff = _Height(root->_right) - _Height(root->_left);if (abs(diff) >= 2){cout << "高度差异常" << endl;return false;}else if (diff != root->_bf){cout << "平衡因子异常" << endl;return false;}return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}int Size(){return _Sise(_root);}int _Sise(Node* _root){if (_root == nullptr)return 0;return 1 + _Sise(_root->_left) + _Sise(_root->_right);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}void RotateR(Node* parent){//调整两个结点的孩子指针Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;//调整三个结点的父亲指针if (subLR) // 结点1subLR->_parent = parent;//提前保存一下parent->_parentNode* parentParent = parent->_parent;parent->_parent = subL; // 结点2//subL的_parent分三种情况if (parent == _root){//parent是根节点_root = subL;subL->_parent = nullptr;}else{subL->_parent = parentParent;if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL; // 结点3}}//调整平衡因子subL->_bf = parent->_bf = 0;}void RotateL(Node* parent){//调整两个结点的孩子指针Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;//调整三个结点的父亲指针if (subRL) // 结点1subRL->_parent = parent;//提前保存一下parent->_parentNode* parentParent = parent->_parent;parent->_parent = subR; // 结点2//subR的parent分三种情况if (parent == _root){//parent是根节点_root = subR;subR->_parent = nullptr;}else{subR->_parent = parentParent;if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR; // 结点3}}//调整平衡因子subR->_bf = parent->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int tembf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//更新平衡因子subLR->_bf = 0;if (tembf == -1)     //情况一{subL->_bf = 0;parent->_bf = 1;}else if(tembf == 1)  //情况二{subL->_bf = -1;parent->_bf = 0;}else if(tembf == 0)  //情况三{subL->_bf = subLR->_bf = 0;}else{assert(false);}}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int tembf = subRL->_bf;RotateR(subR);RotateL(parent);//更新平衡因子subRL->_bf = 0;if (tembf == -1)      //情况一{subR->_bf = 1;parent->_bf = 0;}else if(tembf == 1)   //情况二{subR->_bf = 0;parent->_bf = -1;}else if (tembf == 0)  //情况三{subR->_bf = subRL->_bf = 0;}else{assert(false);}}private:Node* _root = nullptr;
};

test.c:

#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.IsBalanceTree() << endl;
}// 插⼊⼀堆随机值,测试平衡,顺便测试⼀下⾼度和性能等
void TestAVLTree2()
{const int N = 100000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin2 = clock();AVLTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end2 = clock();cout << "Insert:" << end2 - begin2 << endl;cout << t.IsBalanceTree() << endl;cout << "Height:" << t.Height() << endl;cout << "Size:" << t.Size() << endl;size_t begin1 = clock();// 确定在的值for (auto e : v){t.Find(e);}// 随机值//for (size_t i = 0; i < N; i++)//{//	t.Find((rand() + i));//}size_t end1 = clock();cout << "Find:" << end1 - begin1 << endl;
}int main()
{//TestAVLTree1();TestAVLTree2();return 0;
}

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

在这里插入图片描述

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

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

相关文章

C++ 中的 enable_shared_from_this 详解

# C 中的 enable_shared_from_this 详解enable_shared_from_this 是 C 标准库中的一个模板类&#xff0c;用于解决在类的成员函数中需要获取指向自身的 shared_ptr 的问题。## 基本概念当一个对象由 shared_ptr 管理时&#xff0c;如果你想在对象的成员函数中获得一个指向自身的…

day11 - 浮动

1. 标签之间的空白问题 1.1. 问题重现 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Document</title><style>img {width: 100px;}</style> </head> <body><a…

MetaBit基金会加码投资图灵协议,深化去中心化金融与元宇宙生态合作

2025年7月15日 —— 新加坡MetaBit基金会宣布进一步加大对图灵协议&#xff08;Turing Protocol&#xff09;的战略投资&#xff0c;涵盖其去中心化交易所&#xff08;DEX&#xff09;、聚合交易平台&#xff08;CEX&#xff09;及公链生态的技术与资金支持。双方还将围绕元宇宙…

NWinfo(硬件信息检测工具)v1.4.20绿色免费版,U盘随走随检,结果即刻导出

[软件名称]: NWinfo(硬件信息检测工具)v1.4.20绿色免费版 [软件大小]: 1.4 MB [软件大小]: 夸克网盘 | 迅雷网盘 软件介绍 NWinfo 诞生于给老旧机器做体检的需求&#xff1a;一个单文件、零依赖的 Win32 小程序&#xff0c;却能像放大镜一样把机箱里的故事读出来。它不借助…

Numpy科学计算与数据分析:Numpy高效数据处理与优化

Numpy性能优化 学习目标 本课程将深入探讨如何利用Numpy库的特性来优化Python代码的性能&#xff0c;重点讲解向量化操作、避免Python循环等技术&#xff0c;帮助学员掌握高效的数据处理方法。 相关知识点 Numpy性能优化 学习内容 1 Numpy性能优化 1.1 Numpy数组与Pytho…

鸿蒙HarmonyOS中Axios网络库封装与文件上传功能实现

在开发鸿蒙HarmonyOS应用时&#xff0c;网络请求功能是必不可少的。axios是一个非常流行的基于Promise的HTTP客户端&#xff0c;适用于浏览器和Node.js环境。本文将介绍如何在鸿蒙HarmonyOS中封装axios库&#xff0c;使其能够支持文件上传&#xff0c;并提供额外的配置选项以满…

【AI】从零开始的文本分类模型实战:从数据到部署的全流程指南

目录 引言 一、项目背景与目标 二、环境准备 三、数据获取与探索 3.1 数据获取 3.2 数据探索 四、数据预处理 4.1 文本清洗 4.2 分词 4.3 标签编码 4.4 数据集划分 4.5 特征提取 五、模型构建与训练 5.1 逻辑回归模型 5.2 LSTM 模型 六、模型评估 6.1 逻辑回归…

Rust学习心得---特征对象和泛型区别

区别特性泛型&#xff08;静态分发&#xff09;特征对象&#xff08;动态分发&#xff09;决策时机编译时单态化&#xff08;生成具体类型的代码&#xff09;运行时通过vtable查找方法运行性能零运行时开销&#xff08;直接内联调用&#xff09;有额外开销&#xff08;指针跳转…

ESP32-menuconfig(2) -- Application manager

按顺序来说&#xff0c;第二篇本来应该是Security features&#xff0c;但是这块内容应该到小批量才用的到&#xff0c;而一些爱好者可能永远都不会修改这块&#xff0c;所以先看看更常用Application manager&#xff0c;这部分内容也比较少。 Application managerCONFIG_APP_C…

ArgoCD 与 GitOps:K8S 原生持续部署的实操指南

容器技术的爆发让 Kubernetes&#xff08;K8s&#xff09;成为了「云原生时代的操作系统」—— 它能高效编排成千上万的容器&#xff0c;解决弹性伸缩、资源调度等核心问题。但随着企业应用规模扩大&#xff0c;K8s 的「部署与管理」逐渐暴露新的挑战&#xff1a; 多环境&…

Day36--动态规划--1049. 最后一块石头的重量 II,494. 目标和,474. 一和零

Day36–动态规划–1049. 最后一块石头的重量 II&#xff0c;494. 目标和&#xff0c;474. 一和零 遇到难题&#xff0c;思考超过20分钟没有思路的&#xff0c;要跳过&#xff01;不然时间效率太低了。 **看题解同理&#xff0c;看20分钟看不懂的&#xff0c;也要跳过&#xff0…

前端开发技术深度总结报告

前端开发技术深度总结报告 &#x1f4cb; 项目背景 基于 Vue 3 TypeScript Element Plus 的企业级产品管理系统&#xff0c;重点解决产品表单的数据缓存、页面导航、用户体验等核心问题。&#xfffd;&#xfffd; 遇到的问题及解决方案 1. 浏览器控制台错误处理 问题: 大量第…

Linux 单机部署 Kafka 详细教程(CentOS 7+)

系列博客专栏&#xff1a; SpringBoot与微服务实践系列博客Java互联网高级培训教程 一、环境准备 1. 操作系统要求 Kafka 可以在多种 Linux 发行版上运行&#xff0c;本文以 CentOS 7 为例&#xff0c;其他发行版步骤类似&#xff0c;只需调整包管理命令。 2. Java 环境要…

解析工业机器视觉中的飞拍技术

在工业机器视觉的领域&#xff0c;"飞拍"这个术语时常被提起&#xff0c;尤其是在高速检测和动态捕捉的场景中。但你真的了解飞拍是什么吗&#xff1f;它到底如何工作&#xff0c;能为工业应用带来哪些突破性改进呢&#xff1f;让我们一起来解密。1. 飞拍的核心概念 …

[特殊字符]企业游学 | 探秘字节,解锁AI科技新密码

宝子们&#xff0c;想知道全球科技巨头字节跳动的成功秘籍吗&#xff1f;一场企业游学&#xff0c;带你深入字节跳动创新基地&#xff0c;探索AI新科技&#xff0c;揭开规模化增长背后的神秘面纱✨字节跳动&#xff1a;全球经济价值的创造者字节跳动可太牛啦&#xff01;TikTok…

主流大数据框架深度解析:从介绍到选型实战

主流大数据框架深度解析:从介绍到选型实战 在数据驱动的时代,选择合适的大数据处理框架是构建高效、可靠数据平台的关键。 深入剖析 Hadoop MapReduce、Apache Spark、Apache Flink 和 Kafka Streams 四大主流框架,从框架介绍、具体使用场景、优缺点、选择建议到实际案例,…

座舱HMI软件开发架构:核心功能与案例解析

随着智能座舱的持续演进&#xff0c;HMI&#xff08;Human Machine Interface&#xff0c;人与机器交互界面&#xff09;系统已从单一的显示控制器演变为集多屏联动、多模态交互、车载服务集成于一体的智能系统&#xff0c;需要一个多系统、多设备协同运行的复杂架构来支撑。本…

把“思考”塞进 1 KB:我用纯 C 语言给单片机手搓了一个微型 Transformer 推理引擎

标签&#xff1a;TinyML、Transformer、单片机、Cortex-M、量化、KV-Cache、裸机编程 ---- 1. 为什么要在 64 KB SRAM 的 MCU 上跑 Transformer&#xff1f; 2024 年以前&#xff0c;TinyML ≈ CNN CMSIS-NN&#xff0c;做语音唤醒或简单分类就到头了。 但产品同事突然拍脑袋&…

什么是CLI?

什么是CLI&#xff1f;CLI&#xff08;Command Line Interface&#xff09;是命令行界面的缩写&#xff0c;是一种通过文本命令与计算机程序交互的方式。通俗比喻CLI就像是一个"智能助手"&#xff1a;你输入命令&#xff0c;它执行任务就像和机器人对话一样&#xff…

mysql基本sql语句大全

十分想念顺店杂可。。。以下是 MySQL 中常用的基本 SQL 语句大全&#xff0c;按功能分类整理&#xff0c;包含语法和示例&#xff0c;方便参考使用&#xff1a;一、数据库操作&#xff08;DDL&#xff09;用于创建、删除、切换数据库。创建数据库-- 基本语法 CREATE DATABASE […