二叉搜索树进阶--AVL树详细实现过程

news2025/2/26 6:01:37

目录

  • AVL树概念
  • AVL树实现
    • AVL树基础结构
    • 插入
      • 插入:左旋实现
      • 插入:右旋实现
    • AVL树完整实现代码:

之前学习到的二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
因此为了解决这一问题,发明了一种新方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

AVL树概念

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

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
    在这里插入图片描述
    如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度 O ( l o g 2 n ) O(log_2 n) O(log2n)

AVL树实现

AVL树基础结构

  • AVL树节点定义
//AVL树节点定义
template <class T>
struct AVLNode
{
	T _val;
	//平衡因子
	int _bf;
	typedef AVLNode<T> Node;
	Node* _parent;
	Node* _left;
	Node* _right;

	AVLNode(const T& val = T())
		:_val(val)
		,_bf(0)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
	{}
};
  • AVL树定义
template <class T>
class AVLTree
{
public:
	typedef AVLNode<T> Node;

private:
	Node* _root = nullptr;
};

插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子
	bool insert(const T& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_val == val)
				return false;
			else if (cur->_val > val)
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		cur = new Node(val);
		if (parent->_val > val)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//调整,从parent开始
		while (parent)
		{
			//更新parent的平衡因子,如果在左子树插入新节点-1,如果右子树插入+1
			if (parent->_left == cur)
				--parent->_bf;
			else
				++parent->_bf;
			//直到父节点的平衡因子为0时停止更新(平衡因子为0说明插入新节点对祖先父节点的平衡因子不会有影响)
			if (parent->_bf == 0)
				//停止更新
				break;
			//如果平衡因子不为0,则继续向上更新
			else if (parent->_bf == 1 || parent == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					//左边的左边高
					//右旋
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					//右边的右边高
					//左旋
					RotateL(parent);
				}
				break;
			}
		}
		return true;
	}

插入:左旋实现

	//左旋操作结构如下:
	//parent(2)
	//          subR(1)
	//  subRL(0)
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subL->_left;

		subR->_left = parent;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		//更新cur和父亲的父亲之间的连接
		//判断是否为根节点
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}

		//更新父亲的父亲为cur
		parent->_parent = subR;
		//更新平衡因子
		subR->_bf = parent->_bf = 0;
	}

插入:右旋实现

	//右旋操作结构如下:
	//          parent(-2)
	//subL(-1)
	//     subLR(0)
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		subL->_right = parent;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		//更新cur和父亲的父亲之间的连接
		//判断是否为根节点
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		
		//更新父亲的父亲为cur
		parent->_parent = subL;
		//更新平衡因子
		subL->_bf = parent->_bf = 0;
	}

AVL树完整实现代码:

//AVL树节点定义
template <class T>
struct AVLNode
{
	T _val;
	//平衡因子
	int _bf;
	typedef AVLNode<T> Node;
	Node* _parent;
	Node* _left;
	Node* _right;

	AVLNode(const T& val = T())
		:_val(val)
		,_bf(0)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

template <class T>
class AVLTree
{
public:
	typedef AVLNode<T> Node;

	bool insert(const T& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_val == val)
				return false;
			else if (cur->_val > val)
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		cur = new Node(val);
		if (parent->_val > val)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//调整,从parent开始
		while (parent)
		{
			//更新parent的平衡因子,如果在左子树插入新节点-1,如果右子树插入+1
			if (parent->_left == cur)
				--parent->_bf;
			else
				++parent->_bf;
			//直到父节点的平衡因子为0时停止更新(平衡因子为0说明插入新节点对祖先父节点的平衡因子不会有影响)
			if (parent->_bf == 0)
				//停止更新
				break;
			//如果平衡因子不为0,则继续向上更新
			else if (parent->_bf == 1 || parent == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					//左边的左边高
					//右旋
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					//右边的右边高
					//左旋
					RotateL(parent);
				}
				break;
			}
		}
		return true;
	}

	//右旋操作结构如下:
	//          parent(-2)
	//subL(-1)
	//     subLR(0)
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		subL->_right = parent;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		//更新cur和父亲的父亲之间的连接
		//判断是否为根节点
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		
		//更新父亲的父亲为cur
		parent->_parent = subL;
		//更新平衡因子
		subL->_bf = parent->_bf = 0;
	}

	//右旋操作结构如下:
	//          parent(-2)
	//subL(-1)
	//     subLR(0)
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		subL->_right = parent;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		//更新cur和父亲的父亲之间的连接
		//判断是否为根节点
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		
		//更新父亲的父亲为cur
		parent->_parent = subL;
		//更新平衡因子
		subL->_bf = parent->_bf = 0;
	}

	//左旋操作结构如下:
	//parent(2)
	//          subR(1)
	//  subRL(0)
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subL->_left;

		subR->_left = parent;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		//更新cur和父亲的父亲之间的连接
		//判断是否为根节点
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}

		//更新父亲的父亲为cur
		parent->_parent = subR;
		//更新平衡因子
		subR->_bf = parent->_bf = 0;
	}

private:
	Node* _root = nullptr;
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】popen()函数详解

popen函数详解 一、函数介绍二、使用实例 一、函数介绍 头文件#include <stdio.h>函数原型FILE *popen(const char *command, const char *type); 功能&#xff1a;popen()函数通过先创建一个管道&#xff0c;然后调用 fork 产生一个子进程&#xff0c;让子进程执行shel…

nvm的安装,使用及命令

nvm的安装&#xff0c;使用及命令 nvm工具1.nvm的安装基于node的开发nvm是什么nvm下载nvm安装 2.nvm的命令3.nvm的使用 nvm工具 nvm是什么 nvm下载与安装 nvm的基本使用1.nvm的安装 基于node的开发 在介绍nvm之前&#xff0c;先介绍下前端开发中关于node的使用。目前前端不管…

Allegro如何交换两个器件的位置操作指导

Allegro如何交换两个器件的位置操作指导 在用Allegro进行PCB设计的时候,交换两个器件的位置是使用的十分频繁的操作,如下图 需要将两个器件的位置交换,可以手动移动,然后交换下位置,但是Allegro支持快速将两个器件的位置对调 具体操作如下 点击Place点击Swap

【Javascript】数组练习(将字符串“ab,cd,ef,gh“转化成数组,并且删除“cd“)

将字符串"ab,cd,ef,gh"转化成数组&#xff0c;并且删除"cd“ var strab , cd , ef , gh; 调用split函数将字符串转化为数组 var strab , cd , ef , gh;var liststr.split(,);console.log(list); 调用splice方法在数组中删除cd var strab , cd , ef , gh;var …

TCP通信实战案例-模拟BS系统[了解]

前言 1、之前的客户端都是什么样的&#xff1f; 其实就是CS架构&#xff0c;客户端实需要我们自己开发实现的。 2、BS结构是什么样的&#xff0c;需要开发客户端吗&#xff1f; 浏览器访问服务端&#xff0c;不需要开发客户端。 实现BS开发 注意&#xff1a;服务器必须给浏…

C++左值引用与右值引用

0.类型和值类别的区别&#xff1f; 类型&#xff08;type&#xff09;和值类别&#xff08;value category&#xff09; 1.类型指的是数据类型&#xff0c;int&#xff0c;char这样的内置类型&#xff0c;类型主要是用来区别它们的字节大小。除了内置类型还有自定义类型&…

前端构建但没有更新

使用jenkins构建vue前端代码时&#xff0c;构建完成后&#xff0c;jenkins提示构建成功&#xff0c; 但前端刷新提示还是原来的效果&#xff0c;此时需要查看下jenkins构建日志&#xff0c;如果出现下面的文字&#xff0c;说明缺少依赖&#xff0c;最新的代码并没有构建到项目中…

QT-opengl编译错误

问题1&#xff1a; QT编译错误&#xff1a;undefined reference to __imp_gl* 解决方案 在工程*.pro文件中加入 win32:LIBS -lOpengl32 \-lglu32 win32-msvc{LIBS opengl32.lib \glu32.lib \glut.lib}问题2&#xff1a; 解决方案&#xff1a; 改变变量名称&#xff1a; 改…

WebSocket—STOMP详解(官方原版)

WebSocket协议定义了两种类型的消息&#xff08;文本和二进制&#xff09;&#xff0c;但其内容未作定义。该协议定义了一种机制&#xff0c;供客户端和服务器协商在WebSocket之上使用的子协议&#xff08;即更高级别的消息传递协议&#xff09;&#xff0c;以定义各自可以发送…

VM虚拟机 13.5 for Mac

VMware Fusion Pro for Mac是一款强大的虚拟机软件&#xff0c;可以在Mac操作系统中创建、运行和管理多个虚拟机&#xff0c;使用户可以在一台Mac电脑上同时运行多个操作系统和应用程序。 以下是VMware Fusion Pro for Mac的主要特点&#xff1a; 1. 支持多种操作系统&#xff…

CC攻击演示

选择一个代参数网站 未攻击前的cpu 用工具进行CC攻击 执行攻击会把目标主机占用内存&#xff0c;可以在把次数加大一点

(十二)Python文件操作(I/O)

和其它编程语言一样&#xff0c;Python 也具有操作文件&#xff08;I/O&#xff09;的能力&#xff0c;比如打开文件、读取和追加数据、插入和删除数据、关闭文件、删除文件等。 除了提供文件操作基本的函数之外&#xff0c;Python 还提供了很多模块&#xff0c;例如 fileinpu…

前端学成在线项目详细解析三

19-推荐课程-内容样式 HTML结构 <ul><li><a href"#"><div class"pic"><img src"./uploads/course01.png" alt""></div><div class"text"><h4>JavaScript数据看板项目实战…

旋转数组的最小值

文章目录 1 题目2 思路2.1 思路12.2 思路2 3 实现3.1 暴力3.2 二分查找 1 题目 将一个数组最开始的若干元素搬到数组的末尾&#xff0c;称之为数组的旋转。输入一个已排好序数组的一个旋转&#xff0c;求该旋转数组的最小元素。如&#xff0c;数组{3&#xff0c;4&#xff0c;…

linux文件存储之inode,硬链接,软链接详解

1.什么是inode 首先linux一切皆文件&#xff0c;一切皆文本流 inode &#xff0c;中文译名“索引节点”&#xff0c;也叫“i节点” 文件储存在硬盘上&#xff0c;硬盘的最小存储单位叫做”扇区”&#xff08;Sector&#xff09;。每个扇区储存512字节&#xff08;相当于0.5KB&a…

基于Java的校园餐厅订餐管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

塔望3W消费战略全案丨轻食植物基,突围侧翼战

植鲜生 客户&#xff1a;民强&#xff08;昆山&#xff09;食品科技有限公司 品牌&#xff1a;植鲜生 时间&#xff1a;2021年起 项目部分内容保密期 突破传统植物基禁锢 破局轻食新赛道 民强&#xff08;昆山&#xff09;食品科技有限公司是一家集研发、生产、销售为一体…

详细讲解MySQL中的数据类型

详细讲解MySQL中的数据类型 一&#xff0c;整数类型 MySQL中常用的整数类型包括&#xff1a; TINYINT&#xff1a;占用1字节&#xff0c;表示范围为-128到127的有符号整数或0到255的无符号整数。 SMALLINT&#xff1a;占用2字节&#xff0c;表示范围为-32768到32767的有符号整…

众佰诚:开抖音小店需要交保证金吗

随着互联网的发展&#xff0c;越来越多的人选择通过开设网店来创业。抖音作为一款短视频平台&#xff0c;也提供了开店的功能。那么&#xff0c;开抖音小店需要交保证金吗?这是许多创业者关心的问题。 首先&#xff0c;我们需要明确的是&#xff0c;开设抖音小店是需要交纳保证…

【TensorFlow1.X】系列学习笔记【入门四】

【TensorFlow1.X】系列学习笔记【入门四】 大量经典论文的算法均采用 TF 1.x 实现, 为了阅读方便, 同时加深对实现细节的理解, 需要 TF 1.x 的知识 【TensorFlow1.X】系列学习文章目录 文章目录 【TensorFlow1.X】系列学习笔记【入门四】前言损失函数作用均方误差(MSE)交叉熵(C…