【C++】AVL树的实现及测试

news2024/10/7 8:29:21

文章目录

  • AVL树节点的定义
  • AVL树的定义
  • AVL树的插入
  • 插入后更新平衡因子
  • AVL树的右单旋
  • AVL树的左单旋
  • 先左单旋再右单旋
  • 先右单旋再左单旋
  • 检查是否满足AVL树
  • 总代码

AVL树

AVL树也叫平衡二叉搜索树,通过旋转解决了搜索二叉树的不确定性,让整颗树趋近于一颗满二叉树。

1.左右都是一颗AVL树

2.平衡因子的绝对值不会超过1

上图的蓝字表示平衡因子,平衡因子=右子树的高度-左子树的高度

AVL树节点的定义

template<class K,class V>
struct AVLTreeNode
{
	ALVTreeNode<K,V>* _left;
	AVLTreeNode<K,V>* _right;
	AVLTreeNode<K,V>* _parent;//父亲节点

	pair<K, V> _kv;//  key / value

	//构造函数
	AVLTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}

	int _bf;//平衡因子
};

AVL树的定义

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:

private:
	Node* _root=nullptr;
};

AVL树的插入

AVL树的插入和二叉搜索树一样,小的在左边,大的在右边,只是当平衡因子绝对值大于1的时候需要对树进行旋转

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.second)
			{
				//当前值小于要插入的值,往右边走
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.second)
			{
				//当前值大于要插入的值,往左边走
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//有相同的值了,退出插入
				return false;
			}
		}
		//当cur走到了nullptr,就是找到了要插入的点了
		cur = new Node(kv);
		//判断插入在左边还是右边
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;//确定父子关系
	}

插入后更新平衡因子

平衡因子,如果在左边插入节点,那么它的父节点的平衡因子要-1,反之+1

case1:parent节点的平衡因子为0,说明整棵树的高度并没有发生改变,只是补齐了原本缺节点的位置,所以遇到parent->_bf=0的时候,就不需要在修改平衡因子了

case2:parent节点的平衡因子的绝对值为-1,就说明往上走可能存在abs(parent->_bf)=2的,所以要往上一直找。

parent=parent->_parent;

cur=cur->_parent;

如果abs(parent->_bf)=2就需要对其进行旋转来调整树的结构了。

		while (parent)
		{
			//更新平衡因子
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			if (parent->_bf == 0)
			{
				//没有新增高度
				break;
			}
			else if(abs(parent->_bf)==1)
			{
				//平衡因子为1,往上面继续找
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				//需要旋转了
			}
		}

AVL树的右单旋

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

例如上面的抽象图,当平衡树失衡的时候就需要调节平衡了,过程如上图所示。

具体图如下:

这里的操作就是将subL作为一个根节点,将subLR作为parent的左节点(如果subLR存在的话),parent作为subL的右子节点。

左旋的条件是parent->_bf==-2&&cur->_bf==-1

旋转之后parent的平衡因子为0,subL的平衡因子也是0。

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;

		Node* pparent = parent->_parent;

		//防止空指针
		if (subLR)
		{
			subLR->_parent = parent;
		}
		
        subL->_right = parent;
		parent->_parent = subL;
        
		if (parent == _root)
		{
			//如果parent就是根节点
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			//如果parent只是一颗子树的根节点,就还需要连接好parent
			//判断是左还是右节点
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
		subL->_bf = parent->_bf = 0;
	}

AVL树的左单旋

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

方法和右单旋类似。

当**parent->_bf==2&&cur->_bf==1**的时候触发左单旋

void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;

		parent->_right = subRL;

		if (subRL)
		{
			subRL->_parent = subR;
		}

		subR->_left = parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;;
			}
			subR->_parent = pparent;
		}
		subR->_bf = parent->_bf = 0;
	}

可以发现,如果满足左/右单旋的条件都是在同一条直线上,那如果路径不是在同一条直线上呢?

先左单旋再右单旋

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

如果将节点插入到c当中,平衡因子就会发生改变,所以这里的平衡因子需要分情况讨论。

这里通过subLR的平衡因子来确定是在左边插入还是在右边插入。

两种情况下subLR都是0。

下图是最简单的双旋:

void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(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)
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

先右单旋再左单旋

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

C增加节点之后高度和d一样都是h,将其全部旋转到右边去,然后再通过左旋把30压下去,将60作为根节点。

void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL-> _right;
		
		int bf = subLR->_bf;//提前存好,旋转后会subLR会发生改变
		RotateL(parent->_left);
		RotateR(parent);

		subLR->_bf = 0;
		
		if (bf == 1)
		{
			//在右边插入
			parent->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			//已经平衡了
			parent->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			//插入存在问题
			assert(false);
		}
	}

检查是否满足AVL树

通过计算左右子树的高度来确定是否满足AVL树,因为平衡因子是自己设置的,如果还通过平衡因子来确定的话会不太准。

bool _IsBalance(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
			&& _IsBalance(root->_left)//递归左子树
			&& _IsBalance(root->_right);//递归右子树
	}

	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int left = Height(root->_left);
		int right = Height(root->_right);

		return max(left, right) + 1;
	}

手写旋转过程:

总代码

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K,V>* _left;
	AVLTreeNode<K,V>* _right;
	AVLTreeNode<K,V>* _parent;//父亲节点

	pair<K, V> _kv;//  key / value

	//构造函数
	AVLTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}

	int _bf;//平衡因子
};

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.second)
			{
				//当前值小于要插入的值,往右边走
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.second)
			{
				//当前值大于要插入的值,往左边走
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//有相同的值了,退出插入
				return false;
			}
		}
		//当cur走到了nullptr,就是找到了要插入的点了
		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 (abs(parent->_bf) == 1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				// 说明parent所在子树已经不平衡了,需要旋转处理
				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)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				assert(false);
			}
		}


	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

private:

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(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)
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL-> _right;
		
		int bf = subLR->_bf;//提前存好,旋转后会subLR会发生改变
		RotateL(parent->_left);
		RotateR(parent);

		subLR->_bf = 0;
		
		if (bf == 1)
		{
			//在右边插入
			parent->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			//已经平衡了
			parent->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			//插入存在问题
			assert(false);
		}
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;

		parent->_right = subRL;

		if (subRL)
		{
			subRL->_parent = subR;
		}

		subR->_left = parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;;
			}
			subR->_parent = pparent;
		}
		subR->_bf = parent->_bf = 0;
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;

		Node* pparent = parent->_parent;

		//防止空指针
		if (subLR)
		{
			subLR->_parent = parent;
		}

		subL->_right = parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			//如果parent就是根节点
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			//如果parent只是一颗子树的根节点,就还需要连接好parent
			//判断是左还是右节点
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
		subL->_bf = parent->_bf = 0;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

	bool _IsBalance(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
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int left = Height(root->_left);
		int right = Height(root->_right);

		return max(left, right) + 1;
	}

private:

	Node* _root=nullptr;
};

void TestAVLTree1()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };  // 测试双旋平衡因子调节
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}

	t1.InOrder();
	cout << "IsBalance:" << t1.IsBalance() << endl;
}


可以看到是满足AVL树的。

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

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

相关文章

一本通OJ 1810 登山 题解

题目链接 题目大意 从 ( 0 , 0 ) (0,0) (0,0) 走到 ( n , n ) (n,n) (n,n) &#xff0c;不能超过直线 y x yx yx&#xff0c;并且图上有 m m m 个点不能走&#xff0c;问你有几种方案 解题思路 很明显这题与卡特兰数有关&#xff0c;但是不同点在于这题中存在点不能走…

解决阿里云服务器不能访问端口

服务器已经下载了redis&#xff0c;kafka&#xff0c;但就是访问不了端口号&#xff0c; 开通云服务器以后&#xff0c;请一定在安全组设置规则&#xff0c;放行端口 防火墙要关闭

服务器内存满了解决之路

背景&#xff1a;大清早&#xff0c;突然一通电话吵醒&#xff0c;说项目跑不了&#xff0c;还没洗漱赶紧跑过来&#xff0c;毕竟属于实时在用的系统。排查发现系统盘满了&#xff0c;数据写不进去了&#xff0c;导致报错。接手的项目&#xff0c;从来没考虑服务器问题&#xf…

SR501人体红外模块

文章目录 前言一、SR501模块介绍二、设备树添加节点三、驱动程序四、测试程序五、上机测试及效果总结 前言 人体红外模块 是一种能够检测人或动物发射的红外线而输出电信号的传感器。广泛应用于各种自动化控制装置中。比如常见的楼道自动开关、防盗报警等。 一、SR501模块介绍…

深度学习-第R1周心脏病预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 我的环境&#xff1a; 语言环境&#xff1a;Python3.10.7编译器&#xff1a;VScode深度学习环境&#xff1a;TensorFlow 2.13.0 一、前期工作&#xff1a; …

语义通信中基于深度双Q网络的多维资源联合分配算法

目录 论文简介系统模型多维资源联合分配模型多维资源联合分配算法 论文简介 作者 林润韬 郭彩丽 陈九九 王彦君发表期刊or会议 《移动通信》发表时间 2023.4 系统模型 场景中的边缘服务器部署在路边单元上&#xff0c;每个路边单元具有一定的无线覆盖区域&#xff0c;服务器将…

安装mmocr

安装mmocr 一、安装mmdetection 在安装前&#xff0c;如果已经安装过mmcv&#xff0c;先卸载掉&#xff0c;否则不同版本会导致ModuleNotFoundError报错&#xff01; 1、先安装对应版本的pytorch&#xff08;本次cuda10.2&#xff0c;pytorch1.7&#xff09; 2、安装对应版本的…

TableGPT: Towards Unifying Tables, Nature Language and Commands into One GPT

论文标题&#xff1a;TableGPT: Towards Unifying Tables, Nature Language and Commands into One GPT 论文地址&#xff1a;https://github.com/ZJU-M3/TableGPT-techreport/blob/main/TableGPT_tech_report.pdf 发表机构&#xff1a;浙江大学 发表时间&#xff1a;2023 本文…

搭建基于Nginx+Keepalived的高可用web集群并实现监控告警

目录 搭建相关服务器DNS服务器配置WEB服务器配置配置静态IP编译安装nginx 负载均衡器配置lb1lb2高可用配置 NFS服务器配置配置静态IP安装软件包新建共享目录web服务器挂载 监控服务器配置安装node-exporter编写prometheus.yml安装alertmanager和钉钉插件获取机器人webhook编写a…

ubuntu22.04上如何创建有privilege权限,有固定自定义IP的空容器

需求背景&#xff1a; 我想用docker来隔离自己的主机环境&#xff0c;来创建一个隔离的空白全新的开发环境&#xff0c;并且使之有固定的IP&#xff0c;在里面可以自由更新下载各种编译依赖&#xff0c;具有privileged权限的容器&#xff0c;以下是操作实现的具体步骤 查看do…

系统架构设计师-软件架构设计(2)

目录 一、基于架构的软件开发方法&#xff08;ABSD&#xff09; 1、架构需求 1.1 需求获取 1.2 标识构件 1.3 架构需求评审 2、架构设计 2.1 提出架构模型 2.2 映射构件 2.3 分析构件的相互作用 2.4 产生架构 2.5 设计评审 3、架构文档化 4、架构复审 5、架构实现 5.1 分析与…

JVM运行时区域——对象创建内存分配过程

新创建的对象&#xff0c;都存放在伊甸园区域&#xff0c;当垃圾回收时&#xff0c;将伊甸园区域的垃圾数据销毁&#xff0c;然后将存活的对象转移到幸存者0区域&#xff0c;之后创建的新的对象还是存放在伊甸园区域&#xff0c;等到再次垃圾回收后&#xff0c;将伊甸园区域和幸…

《Docker资源限制和调度策略:性能优化与资源管理,打造高效稳定的容器环境》

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

Python 集合 pop()函数使用详解,pop随机删除原理

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 pop函数使用详解 1、随机删除并不完全随机1.1、纯数字1.2、纯字符1.3、混合情况 …

【软件测试】Git 详细实战-远程分支(超细总结)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 远程分支 远程引…

2023云曦期中复现

目录 SIGNIN 新猫和老鼠 baby_sql SIGNIN 签到抓包 新猫和老鼠 看到反序列化 来分析一下 <?php //flag is in flag.php highlight_file(__FILE__); error_reporting(0);class mouse { public $v;public function __toString(){echo "Good. You caught the mouse:&…

使用Selenium与Chrome DevTools交互

目录 为什么我们应该自动化Chrome开发工具&#xff1f; 如何打开Chrome DevTools 元素 控制台 源代码 网络 应用 安全 性能 如何使用Selenium使用chrome Devtools实现自动化&#xff1f; 收集性能指标 使用Selenium DevTols捕获控制台日志 模拟位置 网络节流 捕…

论文精读之Transformer论文

目录 Abstract Conclusion Introduction Background Training Abstract 在一个序列转录模型中&#xff0c;在编码器与译码器之间使用一个“注意力机制”的东西&#xff0c;做了两个机器翻译的实验&#xff0c;效果较其他模型效果更好。 Conclusion Transformer是第一个用于…

全志F1C200S嵌入式驱动开发(调整cpu频率和dram频率)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 f1c200s默认的cpu频率是408M,默认的dram频率是156M。这两个数值,坦白说,都算不上特别高的频率。因为我们的晶振是24M输入,所以408/24=17,相当于整个cpu的频率只是晶振倍频了17…

vue引入自定义字体并使用

结构 font.scss引入字体 charset "UTF-8";font-face{font-family: "caoshu";src: url("caoshu.ttf");font-weight: normal;font-style: normal; }font-face{font-family: "Xingkai";src: url("XingKai.ttf");font-weight: …