C++实现AVL树

news2024/11/24 3:39:20

目录

一、搜索二叉树

1.1 搜索二叉树概念

二、模拟实现二叉搜索树

2.1 框架

2.2 构造函数

2.2.1 构造函数

2.2.2 拷贝构造

2.2.3 赋值拷贝

2.3 插入函数

2.3.1 insert()

2.3.2 RcInsert() 递归实现

2.4 删除结点函数

  2.4.1 Erase()

2.4.2 RcErase()

2.5 中序遍历

2.6 查找函数find()

2.7 析构函数

2.8 测试函数

三、AVL算法实现平衡二叉搜索树

3.1 普通搜索二叉树的性能分析

3.2 AVL树概念与性质

3.3 AVL树结点的定义

3.4 AVL树结点插入

3.5 AVL树旋转算法保持树平衡

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

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

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

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

3.6 判断一个搜索二叉树是否为平衡

3.7 测试AVL树


一、搜索二叉树

1.1 搜索二叉树概念

百度:

搜索二叉树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值

二、模拟实现二叉搜索树

2.1 框架

namespace K
{
    //结点类
	template <class T>
	class BSNode
	{
	public:
		BSNode(const T& data = T())
			:_data(data),
			_left(nullptr),
			_right(nullptr)
		{}
	public:
		T _data;
		BSNode<T>* _left;
		BSNode<T>* _right;
	};

    //搜索二叉树
	template<class T>
	class BSTree
	{
	public:
		typedef BSNode<T> Node;

		BSTree();
		
		BSTree(const BSTree<T>& t);
		
		BSTree<T>& operator=(BSTree<T> tmp);
		
		~BSTree();
	
		bool insert(const T& x = T());

        //中序遍历(从小到大)
		void InOrder();
	
		bool find(const T& x);
		
		bool Erase(const T& x);
	
			
		//recursive 递归实现
		bool RcFind(const T& x);
		
		bool RcInsert(const T& x);
		
		bool RcErase(const T& x);
		
 
      private:
		
		Node* root;
	};
}

2.2 构造函数

2.2.1 构造函数

BSTree()
	:root(nullptr)
{}

2.2.2 拷贝构造

void copyTree(const Node* r)
{
	if (r == nullptr)
		return;
	insert(r->_data);
	copyTree(r->_left);
	copyTree(r->_right);
}

BSTree(const BSTree<T>& t)
	:root(nullptr)
{
	copyTree(t.root);
}


2.2.3 赋值拷贝

BSTree<T>& operator=(BSTree<T> tmp)
{
	swap(root, tmp.root);
	return *this;
}

2.3 插入函数

2.3.1 insert()

bool insert(const T& x = T())
{
	if (root == nullptr)
	{
		root = new Node(x);
		return true;
	}

	//root!=nullprt
	Node* cur = root;
	Node* prev = nullptr;
	while (cur)
	{
		prev = cur;
        //比根大,往右子树走
		if (x > cur->_data)
		{
			cur = cur->_right;
		}
        //比根小,往左子树走
		else if (x < cur->_data)
		{
			cur = cur->_left;
		}
        //相等不符合规则,返回false
		else
			return false;
	}
    //链接(比根小,链左边,比根大链右边)
	cur = new Node(x);
	if (x > prev->_data) prev->_right = cur;
	else prev->_left = cur;
	return true;
}

2.3.2 RcInsert() 递归实现


public:
bool RcInsert(const T& x)
{
	return _RcInsert(root, x);//因为根的私有性,我们用间接调用的方式实现函数功能
}

private:
bool _RcInsert(Node*& root, const T& x)
{
	if (root == nullptr)
	{
		root = new Node(x);
		return true;
	}
	if (x > root->_data) _RcInsert(root->_right, x);
	else if (x < root->_data) _RcInsert(root->_left, x);
	else return false;
}

2.4 删除结点函数


  2.4.1 Erase()

bool Erase(const T& x)
{
	if (root == nullptr)
		return false;
	Node* cur = root;
	Node* prev = nullptr;
	// 找到要删除的结点
	while (cur)
	{
		if (x > cur->_data)
		{
			prev = cur;
			cur = cur->_right;
		}
		else if (x < cur->_data)
		{
			prev = cur;
			cur = cur->_left;
		}
		else break;
	}

    //情况c
	if (cur->_left == nullptr)
	{
		if (prev == nullptr)
		{
			root = cur->_right;
		}
		else
		{
			if (cur->_data > prev->_data)
				prev->_right = cur->_right;
			else prev->_left = cur->_right;
		}
		delete cur;
	}

    //情况b
	else if (cur->_right == nullptr)
	{
		if (prev == nullptr)
		{
			root = cur->_left;
		}
		else
		{
			if (cur->_data > prev->_data)
				prev->_right = cur->_left;
			else prev->_left = cur->_left;
		}
		delete cur;
	}
    
    //情况d
	else
	{
		Node* minRight = cur->_right;
		prev = cur;
		while (minRight->_left)
		{
			prev = minRight;
			minRight = minRight->_left;
		}
		cur->_data = minRight->_data;

        //千万要记得先将minRight的右结点和其父节点链接在一起
		if (minRight == prev->_left)
			prev->_left = minRight->_right;
		else prev->_right = minRight->_right;
		delete minRight;
	}
	return true;
}

2.4.2 RcErase()

public:
bool RcErase(const T& x)
{
   return _RcErase(root, x);
}
private:
bool _RcErase(Node*& root, const T& x)
{
	if (root == nullptr)
		return false;
	if (x > root->_data) _RcErase(root->_right, x);
	else if (x < root->_data) _RcErase(root->_left, x);
	else
	{
		Node* tmp = root;
		if (root->_left == nullptr)
		{
			root = root->_right;
			delete tmp;
		}
		else if (root->_right == nullptr)
		{
			Node* tmp = root;
			root = root->_left;
			delete tmp;
		}
		else
		{
			Node* minRight = root->_right;
			while (minRight->_left)
			{
				minRight = minRight->_left;
			}
			root->_data = minRight->_data;

			//递归删除minright
           _RcErase(root->_right, root->_data);
		}
	}
	return true;
}

2.5 中序遍历

public:
void InOrder()
{
	_InOrder(root);
	cout << endl;
}
private:
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << root->_data << ' ';
	_InOrder(root->_right);
}

2.6 查找函数find()

bool find(const T& x)
{
	if (root == nullptr)
		return false;
	Node* cur = root;
	while (cur)
	{
		if (x > cur->_data)
			cur = cur->_right;
		else if (x < cur->_data)
			cur = cur->_left;
		else return true;
	}
	return false;
}

2.7 析构函数

public:
~BSTree()
{
	Destroy(root);
}
private:
void Destroy(Node* root)
{
	if (root == nullptr)
		return;
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
}

2.8 测试函数

void TestBSTree1()
{
	int arr[] = { 7,3,5,2,1,9,4,8,6 };
	K::BSTree<int> tree;
	for (auto e : arr)
	{
		tree.insert(e);
	}
	tree.InOrder();
	for (int i = 1;i <= 9;i++)
	{
		tree.Erase(i);
		tree.InOrder();
	}
}

void TestBSTree2()
{
	int arr[] = { 7,3,5,2,1,9,4,8,6 };
	K::BSTree<int> tree1;
	for (auto e : arr)
	{
		tree1.RcInsert(e);
	}
	tree1.InOrder();
	K::BSTree<int> tree2;
	tree2 = tree1;
	tree2.InOrder();
}

三、AVL算法实现平衡二叉搜索树

3.1 普通搜索二叉树的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:logN


最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:O(N) 

3.2 AVL树概念与性质

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。

因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

性质:

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

3.AVL树的查找效率为O(logN)


3.3 AVL树结点的定义

template <class T>
struct AVLTreeNode
{
public:
	AVLTreeNode(const T& data)
		:_data(data)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{}
	AVLTreeNode<T>* _left;
	AVLTreeNode<T>* _right;
	AVLTreeNode<T>* _parent;
	T _data;
	int _bf;//树的平衡因子
};

3.4 AVL树结点插入

bool insert(const T& data)
{
   
	if (_root == nullptr)
	{
		_root = new Node(data);
		return true;
	}
    //找到插入位置
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		parent = cur;
		if (data > cur->_data)
			cur = cur->_right;
		else if (data < cur->_data)
			cur = cur->_left;
		else
			return false;
	}
    //插入新节点并建立链接
	cur = new Node(data);
	cur->_parent = parent;
	if (cur->_data > parent->_data)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	//判断平衡因子
	while (parent)
	{
		if (cur == parent->_left)
		{
			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(cur);
			//左高 左右
			else if (parent->_bf == -2 && cur->_bf == 1)
				RotateLR(cur);
            //任何其他情况都直接报错
			else assert(false);
			break;
		}
		else
		{
			assert(false);
		}
	}
	return true;
}

3.5 AVL树旋转算法保持树平衡

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

情况一:左边高且插入结点在父节点左边!

以30结点为轴,将30的右结点与父节点链接,然后将60做30的右结点,这样就可以使树保持为平衡搜索树!

void RotateR(Node* parent)
{
	Node* SubL = parent->_left;//父节点的左孩子
	Node* SubLR = SubL->_right;//左孩子的右孩子
	parent->_left = SubLR;//将左孩子的右孩子与父节点的左链接
	if (SubLR) SubLR->_parent = parent;//右孩子不为空,则找父亲
	//下面准备更新SubL为父节点,记录祖父节点
	Node* gparent = parent->_parent;

	//更新的节点是根节点,则直接改变root
	if (parent == _root)
	{
		_root = SubL;
		SubL->_parent = nullptr;
	}
	else {
		//判断父节点与祖父节点的关系
		if (parent == gparent->_left)
			gparent->_left = SubL;
		else gparent->_right = SubL;
		//与祖父节点链接
		SubL->_parent = parent->_parent;
	}
	//与原父节点链接,其链接在新父节点右
	SubL->_right = parent;
	parent->_parent = SubL;
	//更新平衡因子
	parent->_bf = SubL->_bf = 0;
}

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

情况二:右边高且插入结点在父节点的右边

以60为轴,将60的左结点与父节点30的右链接,将父节点30与60的左链接! 

void RotateL(Node* parent)
{
	Node* SubR = parent->_right;
	Node* SubRL = SubR->_left;
	parent->_right = SubRL;
	if (SubRL) SubRL->_parent = parent;
	Node* gparent = parent->_parent;
	if (parent == _root)
	{
		_root = SubR;
		SubR->_parent = nullptr;
	}
	else {
		if (parent == gparent->_left)
			gparent->_left = SubR;
		else gparent->_right = SubR;
		SubR->_parent = gparent;
	}
	SubR->_left = parent;
	parent->_parent = SubR;
	parent->_bf = SubR->_bf = 0;
}

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

先以60为轴进行左旋,然后以60为轴进行右旋

这里插入新节点后60节点的平衡因子对最后的的30,90平衡因子右影响!

如果60的平衡因子是-1,最后90的平衡因子就是1,30的平衡因子是0。

如果60的平衡因子是1,最后90的平衡因子就是0,30的平衡因子是-1。

如果60的平衡因子是0.最后30,90的平衡因子都是0。

void RotateLR(Node* parent) //parent --> 30节点
{
	Node* SubR = parent->_right;
	int bf = SubR->_bf; //记录插入新节点后的60的平衡因子
	Node* gparent = parent->_parent; //gparent --> 90节点
	RotateL(parent);  //30以60为轴左旋
	RotateR(gparent); //90以60为轴右旋
	if (bf == 1)
	{
		SubR->_bf = 0;
		parent->_bf = 0;
		gparent->_bf = -1;
	}
	else if (bf == -1)
	{
		SubR->_bf = 0;
		parent->_bf = 0;
		gparent->_bf = 1;
	}
	else {
		SubR->_bf = parent->_bf = gparent->_bf = 0;
	}
}

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

先以60为轴进行右旋,然后以60为轴进行左旋!

同样我们30,90最后平衡因子的更新需要判断60的平衡因子!

void RotateRL(Node* parent)
{
	Node* SubL = parent->_left;
	int bf = SubL->_bf;
	Node* gparent = parent->_parent;
	RotateR(parent);
	RotateL(gparent);
	if (bf == 1)
	{
		SubL->_bf = 0;
		parent->_bf = -1;
		gparent->_bf = 0;
	}
	else if (bf == -1)
	{
		SubL->_bf = 0;
		parent->_bf = 0;
		gparent->_bf = 1;
	}
	else {
		SubL->_bf = parent->_bf = gparent->_bf = 0;
	}
}

3.6 判断一个搜索二叉树是否为平衡

//深层遍历,计算每个节点的高度
int TreeHeight(Node* root)
{

	if (root == nullptr)
		return 0;
	int Left_height = TreeHeight(root->_left);
	int Right_height = TreeHeight(root->_right);
	//返回左右子树的最大高度+ 1(自己本身) ==此节点的高度
	return Left_height > Right_height ? Left_height + 1 : Right_height + 1;
}
bool IsBalanceTree(Node* root)
{
	if (root == nullptr)
		return true;
	int Left_height = TreeHeight(root->_left);
	int Right_height = TreeHeight(root->_right);
	//判断 1.此时高度下是否满足平衡 2.左子树是否满足 3.右子树是否满足
	return abs(Left_height - Right_height) <= 1 && IsBalanceTree(root->_left) && IsBalanceTree(root->_right);
}

3.7 测试AVL树


 

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

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

相关文章

Nuxt 3.0 全栈开发:五种数据获取 API 选择和应用最佳实践

Nuxt 3.0 全栈开发 - 杨村长 - 掘金小册核心知识 工程架构 全栈进阶 项目实战&#xff0c;快速精通 Nuxt3 开发&#xff01;。「Nuxt 3.0 全栈开发」由杨村长撰写&#xff0c;299人购买https://s.juejin.cn/ds/S6p7MVo/ 上一讲我们学习了如何基于 API Route 编写接口&#…

12.2 基于Django的服务器信息查看应用(CPU信息)

文章目录CPU信息展示图表展示-视图函数设计图表展示-前端界面设计折线图和饼图展示饼图测试折线图celery和Django配合实现定时任务Windows安装redis根据数据库中的数据绘制CPU折线图CPU信息展示 图表展示-视图函数设计 host/views.py def cpu(request):logical_core_num ps…

【Unity VR开发】结合VRTK4.0:创建人工按钮

语录&#xff1a; 人生需要准备的&#xff0c;不是昂贵的茶&#xff0c;而是喝茶的心情。 前言&#xff1a; 按钮按下抬起是虚仿中经常会出现的功能&#xff0c;那么如何去表现呢&#xff0c;我们可以使用线性变换驱动器对人工按钮进行装箱&#xff0c;以对来自交互器的碰撞做…

Netty——心跳机制与断线重连

心跳机制与断线重连心跳机制IdleStateHandler客户端服务端测试正常情况异常情况总结断线重连为了保证系统的稳定性&#xff0c;心跳机制和断线重连可是必不可少的&#xff0c;而这两个在Netty中也是非常好实现的心跳机制 我们以客户端发送心跳为例&#xff0c;平时我们的心跳实…

linux常用命令介绍 04 篇——uniq命令使用介绍(Linux重复数据的统计处理)

linux常用命令介绍 04 篇——uniq命令使用介绍&#xff08;Linux重复数据的统计处理&#xff09;1. uniq 使用语法2. sort 简单效果3. uniq 使用例子3.1 不加任何选项3.1.1 不用 sort 效果3.1.2 uniq 结合 sort 一起使用3.2 使用选项例子3.2.1 去重打印&#xff08;或打印不重复…

12 nuxt3学习(配置)

链接: nuxt3官网 nuxt简介 vue3技术栈&#xff1a;Nuxt3 是基于 Vue3 Vue Router Vite 等技术栈&#xff0c;全程 Vue3Vite 开发体验&#xff08;Fast&#xff09;。自动导包&#xff1a;Nuxt 会自动导入辅助函数、组合 API和 Vue API &#xff0c;无需手动导入。 基于规范…

卡方分布、非中心卡方分布详解 (概念、求阈值方法、非中心化参数求解办法等)

一、相关概念 1、卡方分布 若n个 相互独立 的随机变量 ξ₁、ξ₂、……、ξn ,均服从 标准正态分布N(0,1),则这n个服从标准正态分布的随机变量的 平方和 构成一新的随机变量,其分布规律称为卡方分布(chi-squaredistribution);其中参数n称为自由度(通俗讲,样本中独立…

有关数据库的一级、二级、三级封锁协议

一级封锁协议 一级封锁协议是指&#xff0c;事务T在修改数据R之前必须先对其加X锁&#xff0c;直到事务结束才释放。事务结束包括正常结束&#xff08;COMMIT&#xff09;和非正常结束&#xff08;ROLLBACK).一级封锁协议可防止丢失修改&#xff0c;并保证事务T是可恢复的。在…

SpringBoot热部署

启动热部署 关于热部署 重启&#xff08;Restart&#xff09;&#xff1a;自定义开发代码&#xff0c;包含类、页面、配置文件等&#xff0c;加载位置restart类加载器重载&#xff08;ReLoad&#xff09;&#xff1a;jar包&#xff0c;加载位置base类加载器开启开发者工具 导…

gma 地理空间绘图:(1) 绘制简单的世界地图-2.设置经纬网

内容回顾 gma 地理空间绘图&#xff1a;(1)绘制简单的世界地图-1.地图绘制与细节调整 方法 AddGridLines(LONRange (-180, 180, 15), LATRange (-90, 90, 15), ShowLON True, ShowLAT True, LineColor ‘gray’, LineStyle (0, (6, 6)), LineWidth 0.3) 功能&#xf…

10种常见网站安全攻击手段及防御方法

在某种程度上&#xff0c;互联网上的每个网站都容易遭受安全攻击。从人为失误到网络罪犯团伙发起的复杂攻击均在威胁范围之内。 网络攻击者最主要的动机是求财。无论你运营的是电子商务项目还是简单的小型商业网站&#xff0c;潜在攻击的风险就在那里。 知己知彼百战不殆&…

MOA-30kV氧化锌避雷器泄露电流测试仪

一、概述 用于检测10kV及以下电力系统用无间隙氧化锌避雷器MOA阀电间接触不良的内部缺陷&#xff0c;测量MOA的直流参考电压&#xff08;U1mA&#xff09;和0.75 U1mA下的泄漏电流。该仪器将直流高压电源、测量和控制系统组成一体&#xff0c;元件浓缩在一个机箱内&#xff0c…

JS混淆加密:Eval的未公开用法

JavaScript奇技淫巧&#xff1a;Eval的未公开用法 作者&#xff1a;http://JShaman.com w2sft&#xff0c;转载请保留此信息很多人都知道&#xff0c;Eval是用来执行JS代码的&#xff0c;可以执行运算、可以输出结果。 但它还有一种未公开的用途&#xff0c;想必很少有人用过。…

【计算机网络】数据链路层:组帧,奇偶校验,CRC循环冗余校验,海明码详解

数据链路层 一、校验 1. 奇偶校验 偶校验 数据位和为偶数&#xff1a;校验位为0&#xff1b;数据位和为奇数&#xff1a;校验位为1&#xff1b; 奇校验 数据位和为奇数&#xff1a;校验位为0&#xff1b;数据位和为偶数&#xff1a;校验位为1&#xff1b; 缺点是会存在误判…

【Autoware】采集实验数据bag包并仿真运行

文章目录1. 官方demo包2. 控制底层地图采集3. 感知定位4. 规划控制5. 仿真或实车运行1. 官方demo包 wget http://db3.ertl.jp/autoware/sample_data/sample_moriyama_data.tar.gz wget http://db3.ertl.jp/autoware/sample_data/sample_moriyama_150324.tar.gz官方示例包的网上…

iPhone屏蔽APP广告的方法

iPhone怎么屏蔽网站(域名)&#xff1f; 操作步骤&#xff1a;“设置” -> “屏幕使用时间” -> “打开屏幕使用时间” -> “内容和隐私访问限制” -> “内容访问限制” -> “网页内容” -> “限制成人网站” -> “永不允许”, 添加需要屏蔽的网站或者域名…

项目开发过程中实际遇到的几个问题处理

1、今天开发环境运行的时候出现下面问题 The project cannot be built until build path errors are resolved. 出现报错情况&#xff0c;之前也没有遇到过。 根据字面意思“在解决生成路径的错误之前&#xff0c;无法生成项目”&#xff0c;也就是说项目的路径位置配置错误…

【论文速递】CVPR2021 - 基于自引导和交叉引导的小样本分割算法

【论文速递】CVPR2021 - 基于自引导和交叉引导的小样本分割算法 【论文原文】&#xff1a;Self-Guided and Cross-Guided Learning for Few-Shot Segmentation 【作者信息】&#xff1a;Bingfeng Zhang, Jimin Xiao , Terry Qin 获取地址&#xff1a;https://openaccess.the…

来香港饮茶吹水先,免费报名Zabbix Meetup香港站!

Zabbix Meetup 来到香港啦&#xff01; 春暖花开&#xff0c;Zabbix计划5月来到香港&#xff0c;和你一起饮茶吹水&#xff01; 时间&#xff1a;5月某日&#xff0c;周几方便&#xff1f; 预计14:00-17:00 形式&#xff1a;线下交流会&#xff0c;免费&#xff0c;线下&…

测评自养号优势,亚马逊,速卖通、美客多、Newegg等跨境卖家必看!

随着跨境电商的发展&#xff0c;越来越多有实力的商家加入到跨境电商的行列&#xff0c;导致行业竞争越来越大&#xff0c;成本投入也越来越高&#xff0c;原来的跨境蓝海已经变红海&#xff0c;卖家都不得不靠“烧钱”来提升排名&#xff0c;吸引流量从而维持销量。那么卖家如…