当前位置: 首页 > news >正文

【高阶数据结构】AVL树

目录

  • 1. 什么是 AVL 树?
  • 2. 为什么需要 AVL 树?
  • 3. AVL树的模拟实现
    • 3.1 AVL树的定义
    • 3.2 AVL树的插入
      • 3.2.1 情况 1:右右情况(RR)(2,1) -> 左单旋
      • 3.2.2 情况 2:左左情况(LL)(-2,-1) -> 右单旋
      • 3.2.3 情况 3:右左情况(RL)(2,-1) -> 右左双旋(先右旋再左旋)
      • 3.2.4 情况 4:左右情况(LR)(-2,1) -> 左右双旋(先左旋再右旋)
    • 3.3 AVL树的求高以及平衡的判断
  • 4. 源代码

1. 什么是 AVL 树?

简单来说,AVL 树是一种自平衡的二叉搜索树(BST)。

它是由两位苏联数学家 G. M. Adelson-Velsky 和 E. M. Landis 在 1962 年发明的,AVL 这个名字就来源于他们姓氏的首字母。

核心思想:在二叉搜索树的基础上,增加了一个平衡条件——对于树中的任意一个节点,其左子树和右子树的高度差不能超过 1。

这个“高度差”有一个专门的名字,叫做 平衡因子。

平衡因子 = 右子树高度 - 左子树高度
平衡因子的值只能是 -1, 0, 1 中的一个。如果出现 2 或 -2,就意味着树不平衡了,需要通过“旋转”来修复。

2. 为什么需要 AVL 树?

普通的二叉搜索树(BST)有一个致命缺点:它的性能严重依赖于插入的顺序。

最佳情况:插入顺序得当,树是完全平衡的,搜索、插入、删除的时间复杂度都是 O(log n)。

最坏情况:如果插入的数据是有序的(例如 1, 2, 3, 4, 5…),BST 会退化成一条链表,时间复杂度变为 O(n)。

AVL 树就是为了解决这个问题而生的。它通过强制维持树的平衡,保证了在最坏情况下,所有操作的时间复杂度仍然是 O(log n)。这个“维持平衡”的过程,就是我们下面要讲的 旋转。

3. AVL树的模拟实现

3.1 AVL树的定义

#pragma once
#include <iostream>
#include <assert.h>using namespace std;template<typename K, typename V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};template<typename K, typename V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:AVLTree():_root(nullptr){}private:Node* _root;
};

3.2 AVL树的插入

关键步骤:

  1. 找到节点的位置
  2. 生成一个节点,并把节点连接好
  3. 以此节点的位置开始更新
      1. 把当前的parent节点平衡因子更新一下(+1或-1)
      1. 若parent节点平衡因子为0,则该树已经平衡,插入已经完成
      1. 若parent节点平衡因子为-1或者1,则继续更迭parent,cur,向上更新
      1. 若parent节点平衡因子为-2或者2,需要进行旋转,一次旋转(无论哪个旋转),都可以让parent的平衡因子直接变成0,即完成了插入。
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}else{Node* parent = nullptr;Node* cur = _root;//1. 找到节点的位置while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}//2. 生成一个节点,并把节点连接好cur = new Node(kv);cur->_parent = parent;//三叉链需要额外链接cur的父亲节点if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}//3. 以此节点的位置开始更新while (parent)//当parent == nullptr的时候,无法继续向上更新,退出即可{//更新parent的平衡因子if (cur->_kv.first > parent->_kv.first){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 0)//此时父亲节点的平衡因子为0,退出即可{break;}else if (parent->_bf == 1 || parent->_bf == -1)//迭代cur和parent,继续{											   //向上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//这里需要进行旋转{											   //怎么判断是什么情况的旋转if (parent->_bf == 2 && cur->_bf == 1)     //以及如何旋转请看后文{RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}else//如果cur的平衡因子不符合为1或-1的情况,说明在插入前树已经不是AVL树了{   //直接断言错误即可assert(false);}break;}else//如果parent的平衡因子不符合为0,1或-1,2或-2的情况{   //说明在插入前树已经不是AVL树了,直接断言错误即可assert(false);}}}return true;
}

接下来,我们讲旋转,讲旋转前,要知道的事情。
当 parent 和 cur 的平衡因子确定时,他们的位置也就确定了。
我们讲的左单璇,parent->_bf == 2 && cur->_bf == 1,即(2,1)的情况。那么简洁操作图解一定是:

    A (平衡因子=2) parent\B (平衡因子=1) cur\C (新插入)

不可能是:

        A (平衡因子=2) parent/B (平衡因子=1)  cur/C (新插入)

因为parent平衡因子为2,那么右子树一定出现了问题,那么cur一定出现在右边,因为cur是从插入点一步步更新过来的。

3.2.1 情况 1:右右情况(RR)(2,1) -> 左单旋

parent的平衡因子为 2 ,cur的平衡因子为 1 时,是需要使用左单旋的情况。
左单璇后,parent、cur 的平衡因子都变成 0
在这里插入图片描述
代码解析:

void RotateL(Node* parent)
{ode* ppnode = parent->_parent;ode* cur = parent->_right; Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{cur->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}parent->_bf = cur->_bf = 0;
}

3.2.2 情况 2:左左情况(LL)(-2,-1) -> 右单旋

parent的平衡因子为 -2 ,cur的平衡因子为 -1 时,是需要使用右单旋的情况。
右单璇后,parent、cur 的平衡因子都变成 0
在这里插入图片描述
代码解析:

void RotateR(Node* parent)
{Node* ppnode = parent->_parent;Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}parent->_bf = cur->_bf = 0;
}

3.2.3 情况 3:右左情况(RL)(2,-1) -> 右左双旋(先右旋再左旋)

parent的平衡因子为 2 ,cur的平衡因子为 -1 时,是需要使用右左单旋的情况。
右左单璇后,parent、cur 的平衡因子不像左旋和右璇一样都变成0。而是有不同情况。

在这里插入图片描述
总结:

旋转前的curleft平衡因子旋转后的parent平衡因子旋转后的cur平衡因子旋转后的curleft平衡因子
第一种情况1-100
第二种情况-1100
第三种情况0000

代码如下:

void RotateRL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(cur);RotateL(parent);if (bf == 0)//此时对应h == 0的情况,尽管我们调用的左单旋和右单旋中已经对parent和cur{           //和curleft的平衡因子进行了无脑置0,但是这里为了解耦,即降低耦合性,所以parent->_bf = 0;//我们需要手动置0,降低耦合性cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else//如果bf出现其它情况,那么插入前该树已经不是AVL树,所以直接断言false即可{assert(false);}
}

3.2.4 情况 4:左右情况(LR)(-2,1) -> 左右双旋(先左旋再右旋)

parent的平衡因子为 -2 ,cur的平衡因子为 1 时,是需要使用左右单旋的情况。
左右单璇后,parent、cur 的平衡因子不像左旋和右璇一样都变成0。而是有不同情况。
其实就是和右左双旋同理。
在这里插入图片描述

总结:

旋转前的curright平衡因子旋转后的parent平衡因子旋转后的cur平衡因子旋转后的curright平衡因子
第一种情况10-10
第二种情况-1100
第三种情况0000

代码如下:

void RotateLR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(cur);RotateR(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}else{assert(false);}
}

3.3 AVL树的求高以及平衡的判断

代码如下:

int Height()
{return Height(_root);
}int Height(Node* root)
{if (root == nullptr){return 0;}int heightL = Height(root->_left);int heightR = Height(root->_right);return  heightL > heightR ? heightL + 1 : heightR + 1;
}bool Isbalance()
{return Isbalance(_root);
}bool Isbalance(Node* root)
{if (root == nullptr){return true;}Node* left = root->_left;Node* right = root->_right;int heightL = Height(left);int heightR = Height(right);int bf = heightR - heightL;if (bf != root->_bf){cout << "平衡因子异常" << root->_kv.first << ':' << root->_bf << endl;return false;}return abs(bf) <= 1 && Isbalance(left) && Isbalance(right);
}

4. 源代码

AVLTree.h:

#pragma once
#include <iostream>
#include <assert.h>using namespace std;template<typename K, typename V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};template<typename K, typename V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:AVLTree():_root(nullptr){}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 (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}while (parent){if (cur->_kv.first > parent->_kv.first){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){if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}else{assert(false);}break;}else{assert(false);}}}return true;}int Height(){return Height(_root);}int Height(Node* root){if (root == nullptr){return 0;}int heightL = Height(root->_left);int heightR = Height(root->_right);return  heightL > heightR ? heightL + 1 : heightR + 1;}bool Isbalance(){return Isbalance(_root);}bool Isbalance(Node* root){if (root == nullptr){return true;}Node* left = root->_left;Node* right = root->_right;int heightL = Height(left);int heightR = Height(right);int bf = heightR - heightL;if (bf != root->_bf){cout << "平衡因子异常" << root->_kv.first << ':' << root->_bf << endl;return false;}return abs(bf) <= 1 && Isbalance(left) && Isbalance(right);}private:void RotateL(Node* parent){Node* ppnode = parent->_parent;Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{cur->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}parent->_bf = cur->_bf = 0;}void RotateR(Node* parent){Node* ppnode = parent->_parent;Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}parent->_bf = cur->_bf = 0;}void RotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(cur);RotateL(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else{assert(false);}}void RotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(cur);RotateR(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}else{assert(false);}}private:Node* _root;
};
http://www.dtcms.com/a/549777.html

相关文章:

  • 三明 网站建设如何建立自己的
  • 可以做兼职的动漫网站公司网站想维护服务器
  • Go语言设计模式:桥接模式详解
  • 前端(Vue3)如何接收后端(SpringBoot)返回的文件并下载
  • 低空经济网络安全体系
  • 福建省建设资格注册中心网站东莞网站推广技巧
  • 汉阳做网站多少钱网站服务器时间查询工具
  • WPF的MVVM模式核心架构与实现细节
  • HarmonyOS 开发高级认证是什么?含金量高吗?
  • 做国外的众筹网站怎么办一个网站
  • 网站设计联盟兰州关键词优化排名
  • 【AI WorkFow】n8n 源码分析-工作流引擎实现原理(五)
  • 技术分享 | torch.profiler:利用探针收集模型执行信息的性能分析工具
  • zynq7000- linux平台 PS读写PL测试
  • 【JavaScript性能优化实战】
  • React Hook为什么这么“严格“?链表内部机制大揭秘
  • 爬虫进阶 JS逆向基础超详细,解锁加密数据
  • GF框架直接使用SQL语句查询数据库的指南
  • 美食网站素材怎么在网上卖产品
  • 网站建设综合实训设计报告怎么做单位网站
  • JavaWeb后端-JDBC、MyBatis
  • 网站访问流程改变WordPress界面
  • 聚合API平台如何重构AI开发效率?
  • 设计模式之单例模式:一个类就只有一个实例
  • 分布式数据库选型指南 (深入对比TiDB与OceanBase)
  • 模板方法模式:优雅地封装算法骨架
  • 有哪些做ppt用图片的网站有哪些免费咨询皮肤科医生在线
  • 理解 MySQL 架构:从连接到存储的全景视图
  • 电商网站 服务器易派客网站是谁做的
  • 大型语言模型(LLM)架构大比拼