目录

一、AVL树概念

1.二叉搜索树的缺点

2.AVL树的概念

二、AVL树定义

1.AVL树节点定义

2.AVL树定义

三、AVL树插入

1.插入节点

2.控制平衡

(1)更新平衡因子

(2)旋转

①右单旋

②左单旋

③左右双旋

④右左双旋

四、AVL树查找

五、AVL树高度

六、判断是否为AVL树

七、AVL树遍历

八、时间复杂度


一、AVL树概念

1.二叉搜索树的缺点

map/multimap/set/multiset的底层都按照二叉搜索树实现,但是在【C++】-- 搜索二叉树一文中已经了解到二叉搜索树的缺点在于,假如向树中插入的元素有序或者接近有序时,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),相当于在顺序表中搜索元素,效率低下。所以map/multimap/set/multiset的底层结构对二叉搜索树做了处理,采用平衡树来实现。

2.AVL树的概念

如何避免二叉树搜索树会退化成单支树的缺点呢?

向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。为什么高度差的绝对值不超过1而不是0呢?因为如果高度差的绝对值不超过0,那么二叉树就变成满二叉树了,因此绝对值不能超过1。这就引入了平衡二叉树的概念:

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

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

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在,搜索时
间复杂度O()。

二、AVL树定义

由于要实现AVL树的增删改查,所以定义AVL树的节点,就需要定义parent,否则插入节点时,不知道要链接到树里面哪个节点下面。

1.AVL树节点定义

节点定义:

#pragma once
#include<iostream>
using namespace std;template<class K,class V>
class AVLTreeNode
{AVLTreeNode<K, V>* _left;//左子树AVLTreeNode<K, V>* _right;//右子树AVLTreeNode<K, V>* _parent;//父亲int _bf;//平衡因子pair<K, V> _kv;//节点AVLTreeNode(const pair<K, V>&, kv){:_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0), _kv(kv)}{}};

2.AVL树定义

template<class K,class V>
struct AVLTree
{typedef AVLTreeNode<K, V> Node;
public://构造函数AVLTree():_root(nullptr){}void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}//重载operator[]V& operator[](const K& key){pair<Node*, bool> ret = Insert(make_pair(key, V()));return ret.first->_kv.second;}//析构函数~AVLTree(){_Destroy(_root);_root = nullptr;}private:Node* _root;
};

三、AVL树插入

1.插入节点

插入节点需要先判断树是否为空:

(1)若为空,让该节点作为根节点

(2)若不为空,分3种情况:

①key比当前节点小,向左走

②key比当前节点大,向右走

③相等,插入失败

如果没找到节点,那么需要插入新节点

 bool Insert(const pair<K, V>& kv){//1.空树if (_root == nullptr){_root = new Node(kv);return true;}//2.非空树Node* parent = _root, * 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 < cur->_kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}return true;}

2.控制平衡

(1)更新平衡因子

一个节点的平衡因子是否需要更新,取决于它的左右子树的高度是否发生变化。插入一个节点,如果它的父亲的平衡因子需要更新,那么它所在这条路径的从父亲到根的所有节点的平衡因子都需要更新。因此

①如果新增节点是父亲的左子树,那么parent->_bf--

②如果新增节点是父亲的右子树,那么parent->_bf++

更新后:

a.如果parent->_bf=0,则停止更新

b.如果parent->_bf==1||-1,需要继续往上更新(说明以parent为根的子树高度变了,由0变成了1或-1,有可能导致parent的parent的平衡因子=2或=-2)

c.如果parent->_bf=2||-2已经不平衡了,那么需要旋转处理

     //控制平衡//1.更新平衡因子while (cur != root){if (parent->_left == cur)//新节点插入到parent左孩子,parent的左子树变高了,平衡因子-1{parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){//已经平衡,停止更新break;}else if(parent->_bf == 1 || parent->_bf == -1){//说明以parent为根的子树高度变了,由0变成了1或-1,有可能影响parent的parent的平衡因子,需要继续往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//已经出现了不平衡,需要旋转处理if (parent->_bf == -2){if (cur->_bf == -1){//右单旋RotateR(parent);}}}else{//插入新节点之前,树已经不平衡了assert(false);}}

(2)旋转

旋转处理有4种:右单旋、左单旋、右左单旋、左右单旋

①右单旋

将新节点插入到较高左子树的左侧,即左左-----右单旋

插入新节点前,AVL树是平衡的,新节点插入到10的左子树,那么10的左子树增加了一层,导致以20为根的二叉树不平衡。为了让20平衡,只能让20的左子树的高度减小一层,并把10的右子树的高度增加一层。

因此,要把10的左子树往上提,把20转下来,因为20比10大,只能把20放在10的右子树,10的右子树比10大,比20小,因此只能把10的右子树放在20的左子树。再更新节点平衡因子。

抽象图:

需要考虑的情况:

(1)10的右孩子可能存在,也可能不存在

(2)20可能是根节点,也可能是子树;如果是根节点,旋转后,要更新根节点。如果是子树,可能是左子树也可能是右子树,就把20原来的父亲的左或右指向10。

 void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = nullptr;if (subL){subLR = subL->_right;}//1.左子树的右子树变我的左子树parent->_left = subLR;if (subLR){subLR->_parent = parent;}//左子树变父亲subL->_right = parent;Node* parentParent = parent->_parent;parent->_parent = subL;if (parent == _root)//parent是根{_root = subL;_root->_parent = nullptr;}else//parent不是根,是子树{if (parentParent->_left == parent){//parent是自己父亲的左子树,将subL作为parent父亲的左孩子parentParent->_left = subL;}else{//parent是自己父亲的右子树,将subL作为parent父亲的右孩子parentParent->_right = subL;}//subL的父亲就是parent的父亲subL->_parent = parentParent;}//更新平衡因子subL->_bf = parent->_bf = 0;}

具象图:

h=0的情况:

20变成10的左子树,10的左子树为空,不用考虑

h=1的情况:

20变成10的右子树,10的右子树12变成20的左子树

h=2的情况:

20变成10的左子树,10的右子树12变成20的左子树​​​​​​​

②左单旋

新节点插入到较高右子树的右侧,即右右-----左单旋

插入新节点前,AVL树是平衡的,新节点插入到20的右子树,那么20的右子树增加了一层,导致以10为根的二叉树不平衡。为了让10平衡,只能让10的右子树的高度减小一层,并把20的左子树的高度增加一层。

因此,要把20的右子树往上提,把10转下来,因为10比20小,只能把10放在20的左子树,20的左子树比20小,比10大,因此只能把20的左子树放在10的右子树。再更新节点平衡因子。

抽象图:

需要考虑的情况:

(1)20的左孩子可能存在,也可能不存在

(2)10可能是根节点,也可能是子树;如果是根节点,旋转后,要更新根节点。如果是子树,可能是左子树也可能是右子树,就把10原来的父亲的左或右指向20。

 void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = nullptr;if (subR){subRL = subR->_left;}//1.右子树的左子树变我的右子树parent->_right = subRL;if (subRL){subRL->_parent = parent;}//2.右子树变父亲subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (parent == _root)//parent是根{_root = parent;_root->_parent = nullptr;}else//parent不是根,是子树{if (parentParent->_left == parent){//parent是自己父亲的左子树,将subR作为parent父亲的左孩子parentParent->_left = subR;}else{//parent是自己父亲的右子树,将subR作为parent父亲的右孩子parentParent->_right = subR;}//subR的父亲就是parent的父亲subR->_parent = parentParent;}//更新平衡因子subR->_bf = parent->_bf = 0;}

具象图:

h=0的情况:

10变成20的左子树,20的左子树为空,不考虑

h=1的情况:

10变成20的左子树,20的左子树变成10的右子树

h=2的情况:

10变成20的左子树,20的左子树变成10的右子树​​​​​​​

③左右双旋

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

插入新节点前,AVL树是平衡的,新节点插入到20的左子树,那么20的左子树增加了一层,导致以30为根的二叉树不平衡。为了让30平衡,只能让30的左子树的高度减小一层。

现在30左子树的高度是h+1,但是现在不能把10进行右单旋,因为要把10右单旋,那么20和30都必须处于10的右子树,这办不到。因此要分为两步:

(1)先把10左单旋

(2)再把20右单旋

void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = 0;if (subLR){bf = subLR->_bf;}RotateL(parent->_left);RotateR(parent);//平衡因子调节还需要分析if (bf == -1){subL->_bf = 0;parent->_bf = 1;subLR->_bf = 0;}else if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}

④右左双旋

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

插入新节点前,AVL树是平衡的,新节点插入到30的右子树,那么30的右子树增加了一层,导致以10为根的二叉树不平衡。为了让10平衡,只能让10的右子树的高度减小一层。

现在10右子树的高度是h+1,但是现在不能把30进行左单旋,因为要把30左单旋,那么10和20都必须处于30的左子树,这办不到。因此要分为两步:

(1)先把20右单旋

(2)再把10左单旋

 void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = 0;if (subRL){bf = subRL->_bf;}//先右旋再左旋RotateR(parent->_right);RotateL(parent);//平衡因子调节还需要具体分析if (bf == 1){parent->_bf = -1;subR->_bf = 0;          subRL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 0){parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);}}

四、AVL树查找

查找比较简单:

(1)如果key比当前节点大,那就继续向右查找;

(2)如果key比当前节点小,那就继续向左查找;

(3)如果key恰好等于当前节点,找到了

 Node* Find(const K& key){Node* cur = _root;while (cur){if (cur.first < key)//比当前节点大,向右查找{cur = cur->_right;}else if (cur.first > key)//比当前节点小,向左查找{cur = cur->_left;}else//等于当前节点,找到了{return cur;}}return nullptr;}

五、AVL树高度

计算树高度肯定要递归计算:

(1)计算左右子树的高度

(2)谁的高度大,那AVL树的高度就为较高子树的高度+1

 //求树的高度int Height(Node* root){if (root == nullptr){return 0;}int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}

六、判断是否为AVL树

检查树是否是AVL树:

(1)获取左右子树高度

(2)根据左右子树高度计算平衡因子

(3)当平衡因子<=2 || -2时就是平衡的

 bool _IsBanlance(Node* root){if (root == nullptr){return true;}int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);//检查平衡因子是否正确if (rightHeight - leftHeight != root->_bf){cout << "平衡因子异常:" << root->_kv.first << endl;return false;}return abs(rightHeight - leftHeight) < 2&& _IsBanlance(root->_left)&& _IsBanlance(root->_right);}//判断是否是AVL树bool IsAVLTree(){return _IsBanlance(_root);}

七、AVL树遍历

遍历也很简单:递归遍历左子树和右子树即可

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

八、时间复杂度

AVL树的操作时,需要找到位置,因此时间复杂度为高度次,即O() 。

【C++】-- AVL树详解相关推荐

  1. 数据结构与算法——二叉平衡树(AVL树)详解

    文章目录 AVL树概念 不平衡概况 四种平衡旋转方式 RR平衡旋转(左单旋转) LL平衡旋转(右单旋转) RL平衡旋转(先右后左双旋转) LR平衡旋转(先左后右单旋转) java代码实现 总结 AVL ...

  2. 平衡二叉树(AVL树)-详解平衡调整

    平衡调整: (注意:平衡调整只是平衡调整,没有进行结点的插入) LL型调整: (带阴影的小框表示插入的结点) 代码如下: AVLNode *AVLTree::LL_Rotate(AVLNode *a) ...

  3. Trie树详解及其应用

    原文:http://blog.csdn.net/hackbuteer1/article/details/7964147 Trie树详解及其应用

  4. B-、B树详解及模拟实现

    B-.B树详解及模拟实现 文章目录 B-.B树详解及模拟实现 一.B-树 二.B树 1.性质 2.特性解释 3.B树的插入操作 4. B树的删除操作 5.B树摸拟实现 一.B-树 B-树就是B树 二. ...

  5. Android 驱动(12)---Linux DTS(Device Tree Source)设备树详解

    Linux DTS(Device Tree Source)设备树详解 Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇) Linux DTS(Device Tr ...

  6. 【学习笔记】线段树详解(全)

    [学习笔记]线段树详解(全) 和三个同学一起搞了接近两个月的线段树,头都要炸了T_T,趁心态尚未凉之前赶快把东西记下来... [目录] [基础]作者:\((Silent\)_\(EAG)\) [懒标记 ...

  7. Jmeter之查看结果树详解

    查看结果树详解 文章目录 查看结果树详解 前言 一.官网介绍 二.具体详解 1.左侧的结果列表 2.右侧的"取样器结果" 3.请求"选项卡 4."响应数据&qu ...

  8. 机器学习算法(二十五):KD树详解及KD树最近邻算法

    目录 1 KD树 1.1 什么是KD树 1.2 KD树的构建 1.3 KD树的插入 1.4 KD树的删除 1.5 KD树的最近邻搜索算法 1.5.1 举例:查询点(2.1,3.1) 1.5.2 举例: ...

  9. 高通平台msm8953 Linux DTS(Device Tree Source)设备树详解之二(DTS设备树匹配过程)

    本系列导航: 高通平台8953  Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇) 高通平台8953 Linux DTS(Device Tree Source ...

最新文章

  1. python基本使用-Python 基本用法
  2. java 输出_使用IntelliJ IDEA2020.2.2 x64新建java项目并且输出Hello World
  3. mysql 强制使用索引_快速找出MySQL数据库冗余索引和未使用索引
  4. Jeewx 捷微管家操作配置文档(开源版本)
  5. oracle12c不能进入到http://localhost:5500/em的解决办法
  6. jar httpclient 少包,此处 区别 common-http包
  7. 标准 C I/O函数
  8. 开发平台(Platform Builder 和 Embedded Visual C++)简介
  9. 最全“勒索病毒”的应对方案
  10. 多组两两比较用什么检验方法_SPSS教程:多个组比较(Fisher精确检验)及组间两两比较...
  11. 对token(令牌)的理解
  12. rumpostgreswk 挖矿病毒程序。杀除
  13. 【有利可图网】PS实战系列:合成奇幻星空下的童话场景
  14. 用Python爬取豆瓣首页所有电影名称、每部电影影评及生成词云
  15. python画分布图(hist)等等
  16. AVI文件花屏问题分析和解决
  17. C语言入门教程第三篇
  18. Oracle数据库巡检模版
  19. 纯css3粉红猪小猪佩奇代码
  20. 建标库标准怎么导出pdf_建标 172-2016-粮食仓库建设标准.pdf

热门文章

  1. 2016版excel_EXCEL表格如何换行
  2. Linux 常用的操作
  3. Matlab代码运行基础文章讲解
  4. 生活小常识日常生活小常识
  5. 酷开与鸿蒙哪个好用,上手说说创维电视与酷开有区别没有?哪个好?老司机透漏评测...
  6. vue报错:Object(...) is not a function
  7. 数据分析师年薪50w起 人才缺口极大
  8. “共享汽车”探索城市交通建设新模式
  9. JVM-从熟悉到精通
  10. 【实用工具】UML图六种箭头的含义