说在前面

今天这篇博客,是博主今年以来最最用心的一篇博客。我们也很久没有更新数据结构系列了,几个月前博主用心深入的学习了这颗二叉平衡搜索树,博主被它的查找效率深深吸引。

AVL树出自1962年中的一篇论文《An_algorithm_for_the_organization_of_information》,它解决了普通二叉搜索树退化的问题,这个博主稍后会详细解释。AVL树放到现在,它的查找效率是很少数据结构能够比拟的。

博主为了这篇博客,做了很多准备,试了很多画图软件,就是为了让大家看得明白!希望大家不要吝啬一键三连啊!!

前言

那么这里博主先安利一下一些干货满满的专栏啦!

手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。

STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482


什么是AVL树?

tips:博主在最后会放一份整体代码,供大家参考!

首先,它是一颗二叉搜索树。

什么是二叉搜索树:

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

如图所示:这就是一颗二叉搜索树。

当我们需要在这颗搜索树里面查找节点4的时候,我们从3开始,比3大,往右走,比5小,往左走。这样我们就找到了4这个节点。

一颗二叉搜索树最高的查找复杂度是O(h),h为树的高度,这样其实我们也可以得到较好的查找效率了。但是,搜索树可能会退化。如图所示:

比如第一棵树,我们如果要查找最下面那个节点,我们要找n次。比如第二棵树,我们找最下面节点要找n/2次,假设我们要查10亿个数据,我们要找5亿次,其实还是O(n)级别。在这种情况下,查找就没有优势了。导致这种情况的主要原因就是,高度不平衡!我们设想一颗满二叉树,它就是平衡的,我们查找一个值,即高度次,复杂度是O(logn)。因此我们需要在插入的同时,不断变换这颗树,使它的高度平衡,这样我们的查找性能才能得到质的提升!一颗二叉平衡搜索树,在10亿个数据中查找一个值,最多查找31次,这种优化,是非常大的!

因此我们引出二叉平衡搜索树,常见的二叉平衡搜索树有AVL树和红黑树。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  • 平衡因子 = 右子树高度 - 左子树高度

AVL树的结构定义

AVL树通常使用三叉链进行构造,我们在处理指针的时候要记得_parent也要处理。

template<class K,class V>
struct AVLNode {
public:
AVLNode<K, V>* _left;
AVLNode<K, V>* _right;
AVLNode<K, V>* _parent;
pair<K, V>_kv;
int _bf; //balance factor
public:
AVLNode(const pair<K, V>& kv)
:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0) {}
}; template<class K, class V>
struct AVL {
typedef AVLNode<K, V>Node;
private:
Node* _root = nullptr;
public:
//成员函数
//...
}

AVL树节点的插入(重点)

AVL树的节点插入可以分为三个步骤:

  1. 新节点的插入
  2. 平衡因子的更新
  3. 通过平衡因子确定平衡性是否被打破,若平衡性被打破,进行旋转

AVL树的旋转:

  1. 左单旋
  2. 右单旋
  3. 左右双旋
  4. 右左双旋

AVL树的旋转是本篇博客最最最最最重点的地方,博主将会详细解释这部分!

一、新节点的插入

新节点的插入步骤和普通二叉搜索树的插入步骤一样,找到插入的位置,直接插入即可!

	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; //先更新平衡因子
//...
//平衡因子更新后,判断是否需要旋转 return true;
}

二、更新平衡因子

当一个新节点被插入之后,该节点的祖先路径上的节点有可能会受到影响,我们要看情况进行更新。如图所示:插入节点后,只会影响祖先路径上节点的平衡因素。

我们只需要利用_parent指针,不断迭代向上就行了。

  • 如果插入在「新节点父亲」的右边,父亲的平衡因子++
  • 如果插入在「新节点父亲」的左边,父亲的平衡因子 - -

tips:博主在最后会放一份整体代码,供大家参考!

如果平衡因子更新后是1/-1,说明子树的高度被改变了,需要继续向上迭代。如果平衡因子更新后是0,说明新节点只是把子树不齐的地方补齐了(这个很好理解,如果大家不明白可以简单画个图),如果更新后平衡因子是2/-2,说明平衡被打破了,需要旋转

        while (parent) {//只有根没有父亲
//最坏可能需要更新到根
if (cur == parent->_right) {
parent->_bf++;
}
else {
parent->_bf--;
} if (parent->_bf == 0) {
//高度不变 -- 停止更新
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) {
//继续更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) {
//说明parent所在的子树已经不平衡了 -- 需要旋转
}
else {
assert(false);//理论上不能走到这里
}
}

三、旋转

AVL树的旋转:

  1. 左单旋
  2. 右单旋
  3. 左右双旋
  4. 右左双旋

首先我们来看几个例子,大概了解一下旋转是一个什么样的操作,看下旋转式怎么让不平衡的树便平衡的:

这里只展示了旋转中的其中一种情况,左单旋,现在博主将分四种情况给大家详细讲解

左单旋:新节点插入较高右子树的右侧

大家注意,这里的触发条件是,较高的是右子树,而且插入的是在该右子树的右侧

其实就是把30拿下来,60替代它的位置即可

此时我们要注意,这里我们发现平衡因子异常的点,也就是30这个节点,它不一定是整棵树的根,它有可能只是一个子树的根,但是在旋转过程中,我们不需要关心它上面的结构是什么,我们旋转完成之后,重新链接上去就行了.

给重要的节点标上名字,怎么旋转的,我们就一目了然了,因为是三叉链条,我们直接操作指针即可!

我们把思路转化成代码:

首先,右右触发左单旋,即parent->_bf==2&&cur->_bf==1(在上图中cur是parent的右孩子),这个时候触发左单旋.

if (parent->_bf == 2 && cur->_bf == 1) {
//注意这里肯定是bf==1的情况 -- 才是单旋
//parent->_bf==2说明是左单旋
rotate_left(parent);//旋转就动了6个指针 -- O(1)
}
	void rotate_left(Node* parent) {
//当然我们还要注意处理parent指针
//parent和subR不可能为空 -- ,但是subRL可能为空
//1.parent是整颗树根
//2.parent是子树的根
//最后更新一下平衡因子
//只有subR和parent的平衡因子受到了影响
Node* subR = parent->_right;
Node* subRL = subR->_left; parent->_right = subRL;
if (subRL) {
subRL->_parent = parent;
}
Node* ppNode = parent->_parent;//记录一下原先parent的parent
subR->_left = parent;
parent->_parent = subR; if (_root == parent) {
_root = subR;
subR->_parent = nullptr;
}
else {
//如果ppNode==nullpt,是不会进来这里的
if (ppNode->_left == parent) {
ppNode->_left = subR;
}
else {
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
//更新一下平衡因子
subR->_bf = parent->_bf = 0;//这个看图就行了
}

右单旋:新节点插入较高左子树的左侧

大家注意,这里的触发条件是,较高的是左子树,而且插入的是在该左子树的左侧

这里其实就是左单旋的一个镜像,博主把图画给大家,代码相信我们已经可以自己写出来了

由图片我们可以得知,触发条件时parent->_bf==-2&&cur->_bf==-1

代码如下:

else if (parent->_bf == -2 && cur->_bf == -1) {
//右单旋
rotate_right(parent);
}
//右单旋 -- 思路和左单旋是镜像 -- 很简单
void rotate_right(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR) {
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (_root == parent) {
_root = subL;
subL->_parent = nullptr;
}
else {
if (ppNode->_left == parent) {
ppNode->_left = subL;
}
else {
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
//更新平衡因子
subL->_bf = parent->_bf = 0;
}

双旋:

看到这里,难道这两种单旋就可以解决所有问题了吗?答案是否定的,我们举个例子:

我们可以看到这种情况,无论怎么单旋,我们都不能使树便平衡.

由此我们引出双旋:

左右双旋:新节点插入较高左子树的右侧:先左旋后右旋

双旋的过程会比单旋的过程复杂一些

其中,左右双旋分别有三种插入的情况,如图所示:

其中情况3:h==0,此时60就是新插入的节点

下面是旋转的过程:

相信上面的图已经把旋转过程解释得非常清晰了,其实就是两次单旋的组合

现在我们要重点讨论旋转后的平衡因子的更新:

我们可以发现最后的平衡因子取决于初始状态下subLR的平衡因子

情况1: subLR->_bf==-1

  • 旋转后parent,subL,subLR的平衡因子分别为1,0,0

情况2: subLR->_bf==1

  • 旋转后parent,subL,subLR的平衡因子分别为0,-1,0

情况3: subLR->_bf==0

  • 旋转后parent,subL,subLR的平衡因子分别为0,0,0

现在我们只需要对照着图片,对照着调整后的平衡因子,就可以很快的写出代码:

首先,通过图片我们可以知道,左右双旋的触发条件是:

parent->_bf == -2 && cur->_bf == 1

else if (parent->_bf == -2 && cur->_bf == 1) {
//左右双旋
rotate_left_right(parent);
}
    void rotate_left_right(Node* parent) {
//要在单旋之前记录一下,因为单旋之后平衡因子会变
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;//记录一下subLR的平衡因子
rotate_left(parent->_left);//先最左边进行一个左旋
rotate_right(parent);//再对自己进行一个右旋转
//如何区分三种情况的平衡因子更新呢? subLR->_bf = 0;//一定要画图!三种情况的subLR最终都是0
if (bf == 1) {
//情况1
parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1) {
//情况2
parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == 0) {
//情况3
parent->_bf = 0;
subL->_bf = 0;
}
else assert(false);
}

右左双旋:新节点插入较高右子树的左侧:先右旋后左旋

同样,右左双旋也有三种情况.

右左双旋其实就是左右双旋的一个镜像,搞明白了左右双旋,右左双旋直接画一下图,总结一下三种情况的平衡因子,直接写代码就行了.

触发条件:

parent->_bf == 2 && cur->_bf == -1

else if (parent->_bf == 2 && cur->_bf == -1) {
//右左双旋
rotate_right_left(parent);
}
    void rotate_right_left(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
rotate_right(parent->_right);
rotate_left(parent);
subRL->_bf = 0;
if (bf == 1) {
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1) {
subR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 0) {
subR->_bf = 0;
parent->_bf = 0;
}
else assert(false);
}

四、AVL树的检验

写到这里,我们就可以尝试插入一些节点,检查AVL树是否平衡了

当然,我们可以通过调试,打断点去检查这棵树,但是这样很麻烦.

同时,我们是不能通过检查中序遍历是否有序去判断AVL树的合法性的.因为所有的搜索树中序遍历都是有序的.

我们要通过AVL树的性质去检查,检查每颗子树的左右子树高度差是否小于等于1:

当然现在写出这种代码对于我们来说其实很简单了,这里也提供一道里扣题的传送门,其实就是AVL树的检验,大家可以顺便完成它

面试题 04.04. 检查平衡性https://leetcode.cn/problems/check-balance-lcci/这里博主也一起提供中序遍历的代码:

class AVL {
//...
//...
//...
public:
void inorder() {
_inorder(this->_root);
}
bool is_balance() {
return _is_balance(this->_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 leftHT = _height(root->_left);
int rightHT = _height(root->_right);
return max(leftHT, rightHT) + 1;
}
bool _is_balance(Node* root) {
if (root == nullptr)return true;
int leftHT = _height(root->_left);//左子树高度
int rightHT = _height(root->_right);//右子树高度
int diff = rightHT - leftHT;
//把平衡因子也检查一下
if (diff != root->_bf) {
cout << root->_kv.first << "的平衡因子异常" << endl;
return false;
}
return abs(diff) < 2
&& _is_balance(root->_left)//判断一下左子树是否平衡
&& _is_balance(root->_right);//判断一下右子树是否平衡
}
}

五、删除等接口

讲到这里有伙伴可能会问,为什么讲AVL,不讲删除那些接口呢?

因为,校招,公司面试,以后工作中都基本不会考察到AVL树的删除接口,红黑树也是一样,我们只需要掌握插入接口就行了.

AVL树,红黑树我们都是做了解性学习,我们并不需要去手撕它的全部代码,这样时间成本很大,意义不大.我们学习AVL树,我们需要深入的去理解它的结构,学习一个插入接口,我们已经可以很好的做到这一点了.

这里博主大概讲一下删除的步骤:

一开始也是像搜索树一样找到要删除的节点,用替换法删除,删除后也是同样,检查平衡性.如果平衡性被破坏,进行旋转,这个过程其实就大概是插入的反方向操作

六、AVL.h整体代码

#pragma once

#include<map>
#include<set>
#include<algorithm>
#include<assert.h>
#include<time.h>
using namespace std; template<class K,class V>
struct AVLNode {
public:
AVLNode<K, V>* _left;
AVLNode<K, V>* _right;
AVLNode<K, V>* _parent;
pair<K, V>_kv;
int _bf; //balance factor
public:
AVLNode(const pair<K, V>& kv)
:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0) {}
}; //如何更新平衡因子
//如何旋转 template<class K, class V>
struct AVL {
typedef AVLNode<K, V>Node;
private:
Node* _root = nullptr;
private:
//左单旋
void rotate_left(Node* parent) {
//当然我们还要注意处理parent指针
//parent和subR不可能为空 -- ,但是subRL可能为空
//1.parent是整颗树根
//2.parent是子树的根
//最后更新一下平衡因子
//只有subR和parent的平衡因子受到了影响
Node* subR = parent->_right;
Node* subRL = subR->_left; parent->_right = subRL;
if (subRL) {
subRL->_parent = parent;
}
Node* ppNode = parent->_parent;//记录一下原先parent的parent
subR->_left = parent;
parent->_parent = subR; if (_root == parent) {
_root = subR;
subR->_parent = nullptr;
}
else {
//如果ppNode==nullpt,是不会进来这里的
if (ppNode->_left == parent) {
ppNode->_left = subR;
}
else {
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
//更新一下平衡因子
subR->_bf = parent->_bf = 0;//这个看图就行了
}
//右单旋
void rotate_right(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR) {
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (_root == parent) {
_root = subL;
subL->_parent = nullptr;
}
else {
if (ppNode->_left == parent) {
ppNode->_left = subL;
}
else {
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
//更新平衡因子
subL->_bf = parent->_bf = 0;
}
//左右双旋
void rotate_left_right(Node* parent) {
//要在单旋之前记录一下,因为单旋之后平衡因子会变
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;//记录一下subLR的平衡因子
rotate_left(parent->_left);//先最左边进行一个左旋
rotate_right(parent);//再对自己进行一个右旋转
//如何区分三种情况的平衡因子更新呢? subLR->_bf = 0;//一定要画图!三种情况的subLR最终都是0
if (bf == 1) {
//情况1
parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1) {
//情况2
parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == 0) {
//情况3
parent->_bf = 0;
subL->_bf = 0;
}
else assert(false);
}
//右左双旋
void rotate_right_left(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
rotate_right(parent->_right);
rotate_left(parent);
subRL->_bf = 0;
if (bf == 1) {
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1) {
subR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 0) {
subR->_bf = 0;
parent->_bf = 0;
}
else assert(false);
}
public:
//我们先不返回pair,到时候我们封装map的时候在搞
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 (cur == parent->_right) {
parent->_bf++;
}
else {
parent->_bf--;
} if (parent->_bf == 0) {
//高度不变 -- 停止更新
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) {
//继续更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) {
//说明parent所在的子树已经不平衡了 -- 需要旋转
//左旋
if (parent->_bf == 2 && cur->_bf == 1) {
//注意这里肯定是bf==1的情况 -- 才是单旋
//parent->_bf==2说明是左单旋
rotate_left(parent);//旋转就动了6个指针 -- O(1)
}
else if (parent->_bf == -2 && cur->_bf == -1) {
rotate_right(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1) {
//左右双旋
rotate_left_right(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1) {
//右左双旋
rotate_right_left(parent);
}
else assert(false);
break;
}
else {
assert(false);//理论上不能走到这里
}
}
return true;
}
public:
void inorder() {
_inorder(this->_root);
}
bool is_balance() {
return _is_balance(this->_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 leftHT = _height(root->_left);
int rightHT = _height(root->_right);
return max(leftHT, rightHT) + 1;
}
bool _is_balance(Node* root) {
if (root == nullptr)return true;
int leftHT = _height(root->_left);//左子树高度
int rightHT = _height(root->_right);//右子树高度
int diff = rightHT - leftHT;
//把平衡因子也检查一下
if (diff != root->_bf) {
cout << root->_kv.first << "的平衡因子异常" << endl;
return false;
}
return abs(diff) < 2
&& _is_balance(root->_left)//判断一下左子树是否平衡
&& _is_balance(root->_right);//判断一下右子树是否平衡
}
}; void test1() {
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
AVL<int, int>t1;
for (auto e : a) {
t1.insert(make_pair(e, e));
}
t1.inorder();
cout << "is_balance():" << t1.is_balance() << endl;
}
void test2(){
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVL<int, int>t1;
for (auto e : a) {
t1.insert(make_pair(e, e));
}
t1.inorder();
cout << "is_balance():" << t1.is_balance() << endl;
}
void test3(){
size_t N = 10000;
srand(time(nullptr));
AVL<int, int>t1;
for (size_t i = 0; i < N; ++i) {
int x = rand();
t1.insert(make_pair(x, i));
}
cout << "is_balance():" << t1.is_balance() << endl;
}

七、总结

看到这里,大家应该对AVL树的实现,重点是它的旋转有了比较深入的了解了。这篇博客博主花了很多心思在画图上,也投入了很多时间到画图上。下期给大家带来红黑树的内容。希望大家可以多多支持,一键三连,点赞关注收藏评论后在离开哦!

( 转载时请注明作者和出处。未经许可,请勿用于商业用途 )
更多文章请访问我的主页

@背包https://blog.csdn.net/Yu_Cblog?type=blog

万字手撕AVL树 | 上百行的旋转你真的会了吗?【超用心超详细图文解释 | 一篇学会AVL】的更多相关文章

  1. jQuery Cookie (内附 上百行的中文使用手册,与 所有的注释中文翻译)

    jQuery Cookie (内附 上百行的中文使用手册,与 所有的注释中文翻译) 博主亲自翻译. 大家多多捧场. 更多资源请点击"查看TA 的资源" .全场通通 2积分. htt ...

  2. 【SecureCRT配置】修改默认卷屏行数当做一个操作,屏幕输出有上百行,当需要将屏幕回翻时,这个设置会有很大帮助,默认为500行,可以改为10000行,不用担心找不到了。 选项 => 全局选项 => Default Session => Edit Default Settings => Terminal => Emulation => Scrollback 修改为32000。

    SecureCRT配置屏幕内容输出到log文件 SecureCRT看不到前几分钟操作的内容,或者想把通过vi命令查看的日志输出到log文件(在懒得下载日志文件的情况下),所以接下来就这样操作: 文件保 ...

  3. AVL树的插入操作(旋转)图解

    =================================================================== AVL树的概念       在说AVL树的概念之前,我们需要清楚 ...

  4. 手写AVL 树(上)

    平衡二叉树 左旋,右旋,左右旋,右左旋 具体原理就不说了,网上教程很多.这里只实现了建树的过程,没有实现删除节点的操作. 下一篇会实现删除节点的操作. // // main.cpp // AVL // ...

  5. AVL树(三)之 Java的实现

    概要 前面分别介绍了AVL树"C语言版本"和"C++版本",本章介绍AVL树的Java实现版本,它的算法与C语言和C++版本一样.内容包括:1. AVL树的介绍 ...

  6. AVL树(一)之 图文解析 和 C语言的实现

    概要 本章介绍AVL树.和前面介绍"二叉查找树"的流程一样,本章先对AVL树的理论知识进行简单介绍,然后给出C语言的实现.本篇实现的二叉查找树是C语言版的,后面章节再分别给出C++ ...

  7. AVL树(二)之 C++的实现

    概要 上一章通过C语言实现了AVL树,本章将介绍AVL树的C++版本,算法与C语言版本的一样. 目录 1. AVL树的介绍2. AVL树的C++实现3. AVL树的C++测试程序 转载请注明出处:ht ...

  8. AVL树的实现——c++

    一.概念 AVL树是根据它的发明者G.M. Adelson-Velsky和E.M. Landis命名的.它是最先发明的自平衡二叉查找树,也被称为高度平衡树.相比于"二叉查找树",它 ...

  9. 006-数据结构-树形结构-二叉树、二叉查找树、平衡二叉查找树-AVL树

    一.概述 树其实就是不包含回路的连通无向图.树其实是范畴更广的图的特例. 树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合. 1.1.树的特性: 每个结点有零个或多个子 ...

  10. linux 内核数据结构之 avl树.

    转载: http://blog.csdn.net/programmingring/article/details/37969745 https://zh.wikipedia.org/wiki/AVL% ...

随机推荐

  1. 0x41 数据结构进阶-并查集

    A题 程序自动分析 题目链接:https://ac.nowcoder.com/acm/contest/1031/A 题目描述 在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足. 考 ...

  2. AtCoder Beginner Contest 210 (A~E)

    比赛链接:Here A - Cabbages 略 B - Bouzu Mekuri 略 C - Colorful Candies 用map维护连续一段区间的不同元素即可. int main() { c ...

  3. 判断客户端是PC还是移动端问题的解决方案

    今天在帮 莲的Live 2D 做浏览器适配的时候学会的一段代码 利用 Javascript 进行判断 function isPC() { //是否为PC端 var userAgentInfo = na ...

  4. 聊聊与前端工程师天然互补的 Serverless

    作者:灵轮_(阿里云前端技术专家)_ 作为前端工程师,我们的使命是为用户提供良好的前端用户体验.随着云原生时代的到来,显而易见的,我们能做的更多了.Serverless 产品的特点是免运维.按量付费和 ...

  5. 无法访问Github仓库的极简治标法

    技术背景 由于IP原因,国内的IP访问Github仓库的时候会经常遇到一些困难,甚至存在无法Ping通的情况.尝试过FastGithub等方案,但还是会遇到各种各样的问题,导致情况越来越复杂. 但是转 ...

  6. 02-UPF-Power Domain

    How do we use power domain 使用UPF至少有一个power domain power domain可以进行嵌套 Defining Power Domain Example P ...

  7. SkyWalking的学习之二(性能优化以及log)

    SkyWalking的学习之二(性能优化以及log) 背景 周六在家学习了SkyWalking的交单部署和agent的方式获取日志. 万恶的周天上班到公司发现出现了宕机. 具体原因是我想进行SkyWa ...

  8. [转帖]使用 Logical Import Mode

    https://docs.pingcap.com/zh/tidb/v6.5/tidb-lightning-logical-import-mode-usage 配置及使用 可以通过以下配置文件使用 Lo ...

  9. [转帖]sqlplus与shell互相传值的几种情况

    https://www.cnblogs.com/youngerger/p/9068888.html sqlplus与shell互相传值的几种情况 情况一:在shell中最简单的调用sqlplus $c ...

  10. [转帖]Kafka—配置SASL/PLAIN认证客户端及常用操作命令

    介绍   SASL/PLAIN 是一种简单的 username/password安全认证机制,本文主要总结服务端开启该认证后,命令行客户端进行配置的操作流程. 配置 增加jaas.properties ...