【数据结构】平衡二叉树

news2024/9/28 23:33:20


目录

一、平衡二叉树的介绍

二、平衡二叉树的插入

1、平衡二叉树的插入步骤

2、平衡二叉树的旋转

2.1左单旋

2.2右单旋

2.3左右双旋

2.4右左双旋

三、平衡二叉树的删除(略)

四、个人对平衡二叉树见解

五、平衡二叉树整体代码


一、平衡二叉树的介绍

二叉搜索树的目的是为了提高查找效率,但如果数据有序或者接近有序,那么二叉搜索树将会变成单支树,查找元素的效率等效为顺序表,树形结构的优势荡然无存。

为了解决这一问题,苏联数学家G.M.Adelson-Velskii和E.M.Landis便发明了平衡二叉树(AVL树)。

平衡二叉树:在一棵搜索二叉树中每个节点的左右子树的高度差的绝对值不超过1。左右子树的高度差被称为平衡因子(平衡因子=右子树高度-左子树高度)若一颗平衡二叉树的节点个数为n,那么其高度为logN。

二、平衡二叉树的插入

1、平衡二叉树的插入步骤

平衡二叉树的插入第一步和二叉搜索树一样,根据二叉搜索树的特性,找到新插入节点位于整棵树的位置。

随后使用逻辑语句判断新节点是插入在父节点的左还是右,并维护其与父节点的指针关系。

新增在右,平衡因子++;新增在左,平衡因子--。那新插入了一个节点,原先的平衡二叉树的结构可能会遭到破坏,所以需要观察平衡因子的三种情况,进行分类讨论:如图

情况三如何旋转?无非是四种情况:

2、平衡二叉树的旋转

当出现上图情况三时,就需要对平衡二叉树的节点进行旋转,旋转的目的是要让这颗树继续维持平衡二叉树的形态,同时调节子树的高度,让其和插入前的高度保持一致,旋转后不要忘记更新那些被旋转的节点的平衡因子。

2.1左单旋

5可能是根,也可能是某颗子树的根,分类讨论:

void RotateLeft(Node* parent)//左单旋
{
	Node* grandfather = parent->_parent;
	Node* cur = parent->_right;
	if (parent == _root)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (grandfather->_left == parent)//需要判定parent原来属于grandfather的哪一边
			grandfather->_left = cur;
		else
			grandfather->_right = cur;
		cur->_parent = grandfather;
	}
	parent->_right = cur->_left;
	if (cur->_left != nullptr)
		cur->_left->_parent = parent;
	cur->_left = parent;
	parent->_parent = cur;
	//更新平衡因子
	cur->_bf = parent->_bf = 0;
}

2.2右单旋

void RotateRight(Node* parent)//右单旋
{
	Node* grandfather = parent->_parent;
	Node* cur = parent->_left;
	if (parent == _root)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (grandfather->_left == parent)
		{
			grandfather->_left = cur;
			cur->_parent = grandfather;
		}
		else
		{
			grandfather->_right = cur;
			cur->_parent = grandfather;
		}
	}
	parent->_parent = cur;
	parent->_left = cur->_right;
	if (cur->_right != nullptr)
		cur->_right->_parent = parent;
	cur->_right = parent;
	//更新平衡因子
	cur->_bf = parent->_bf = 0;
}

2.3左右双旋

void RotateLR(Node* parent)//左右双旋
{
	Node* cur = parent->_left;
	Node* curR = cur->_right;
	int bf = curR->_bf;

	RotateLeft(parent->_left);
	RotateRight(parent);
	if (bf == -1)//curR的左树插入新节点
	{
		cur->_bf = 0;
		parent->_bf = 1;
		curR->_bf = 0;
	}
	else if (bf == 1)//curR的右树插入新节点
	{
		cur->_bf = -1;
		parent->_bf = 0;
		curR->_bf = 0;
	}
	else if (bf == 0)//curR自身为新增节点
	{
		cur->_bf = 0;
		parent->_bf = 0;
		curR->_bf = 0;
	}
	else
		assert(false);//不可能出现这种情况
}

2.4右左双旋

void RotateRL(Node* parent)//右左双旋
{
	Node* cur = parent->_right;
	Node* curL = cur->_left;
	int bf = curL->_bf;

	RotateRight(parent->_right);
	RotateLeft(parent);
	if (bf == -1)//curL的左树插入新节点
	{
		cur->_bf = 1;
		parent->_bf = 0;
		curR->_bf = 0;
	}
	else if (bf == 1)//curL的右树插入新节点
	{
		cur->_bf = 0;
		parent->_bf = -1;
		curR->_bf = 0;
	}
	else if (bf == 0)//curL自身为新增节点
	{
		cur->_bf = 0;
		parent->_bf = 0;
		curR->_bf = 0;
	}
	else
		assert(false);//不可能出现这种情况
}

三、平衡二叉树的删除(略)

按照二叉搜索树的方式对平衡二叉树节点进行删除。更新平衡因子时,平衡因子为1或-1便可以停止向上更新。当平衡因子绝对值大于1时,同样需要进行旋转解决。

四、个人对平衡二叉树见解

平衡二叉树强就强在通过大量的旋转控制整颗树任意一个节点的左右子树高度差不大于1,使树的结构近似完全二叉树,搜索效率为logN。

但偏偏是频繁的旋转,导致其插入删除的效率并不及红黑树,这也是红黑树成为树形容器的原因。

但是一颗树仅用来查找而不进行删除的话,用平衡二叉树还是很棒的。

五、平衡二叉树整体代码

#pragma once
#include <iostream>
#include <cassert>
#include <map>
#include <set>
#include <time.h>
using namespace std;

template <class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;//Balance factor平衡因子
	AVLTreeNode(const pair<K, V>& kv)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

template <class K, class V>
struct AVLTree
{
	typedef AVLTreeNode<K, V> Node;//记住不要把这个<K,V>漏了!!!
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		//_root不为空
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else//相等说明元素相同,插入失败
				return false;
		}
		//开始插入
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;//维护cur的父指针
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		//更新平衡因子
		while (parent)//最坏情况更新到根
		{
			if (cur == parent->_left)
			{
				--parent->_bf;
			}
			else if (cur == parent->_right)
			{
				++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)//父节点的平衡因子为2或-2,不满足平衡二叉树规则
			{
				//需要旋转调整
				if (parent->_bf == 2 && cur->_bf == 1)//触发左单旋条件
				{
					RotateLeft(parent);//左单旋
				}
				else if (parent->_bf == -2 && cur->_bf == -1)//触发右单旋条件
				{
					RotateRight(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//防止出现代码错误导致平衡因子绝对值出现大于3的情况(正常情况不会发生这种现象)
			{
				assert(false);
			}
		}
		return true;
	}
	void RotateLeft(Node* parent)//左单旋
	{
		Node* grandfather = parent->_parent;
		Node* cur = parent->_right;
		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (grandfather->_left == parent)//需要判定parent原来属于grandfather的哪一边
				grandfather->_left = cur;
			else
				grandfather->_right = cur;
			cur->_parent = grandfather;
		}
		parent->_right = cur->_left;
		if (cur->_left != nullptr)
			cur->_left->_parent = parent;
		cur->_left = parent;
		parent->_parent = cur;
		//处理平衡因子
		cur->_bf = parent->_bf = 0;
	}
	void RotateRight(Node* parent)//右单旋
	{
		Node* grandfather = parent->_parent;
		Node* cur = parent->_left;
		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (grandfather->_left == parent)
			{
				grandfather->_left = cur;
				cur->_parent = grandfather;
			}
			else
			{
				grandfather->_right = cur;
				cur->_parent = grandfather;
			}
		}
		parent->_parent = cur;
		parent->_left = cur->_right;
		if (cur->_right != nullptr)
			cur->_right->_parent = parent;
		cur->_right = parent;
		//更新平衡因子
		cur->_bf = parent->_bf = 0;
	}
	void RotateLR(Node* parent)//左右双旋
	{
		Node* cur = parent->_left;
		Node* curR = cur->_right;
		int bf = curR->_bf;

		RotateLeft(parent->_left);
		RotateRight(parent);
		if (bf == -1)//curR的左树插入新节点
		{
			cur->_bf = 0;
			parent->_bf = 1;
			curR->_bf = 0;
		}
		else if (bf == 1)//curR的右树插入新节点
		{
			cur->_bf = -1;
			parent->_bf = 0;
			curR->_bf = 0;
		}
		else if (bf == 0)//curR自身为新增节点
		{
			cur->_bf = 0;
			parent->_bf = 0;
			curR->_bf = 0;
		}
		else
			assert(false);//不可能出现这种情况
	}
	void RotateRL(Node* parent)//右左双旋
	{
		Node* cur = parent->_right;
		Node* curL = cur->_left;
		int bf = curL->_bf;

		RotateRight(parent->_right);
		RotateLeft(parent);
		if (bf == -1)//curL的左树插入新节点
		{
			cur->_bf = 1;
			parent->_bf = 0;
			curL->_bf = 0;
		}
		else if (bf == 1)//curL的右树插入新节点
		{
			cur->_bf = 0;
			parent->_bf = -1;
			curL->_bf = 0;
		}
		else if (bf == 0)//curL自身为新增节点
		{
			cur->_bf = 0;
			parent->_bf = 0;
			curL->_bf = 0;
		}
		else
			assert(false);//不可能出现这种情况
	}
	
	int TreeHight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftHight = TreeHight(root->_left);
		int rightHight = TreeHight(root->_right);
		return leftHight > rightHight ? leftHight + 1 : rightHight + 1;
	}
	void Inorder()
	{
		_Inorder(_root);
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
private:
	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 leftHight = TreeHight(root->_left);
		int rightHight = TreeHight(root->_right);
		//检查平衡因子对不对
		if (rightHight - leftHight != root->_bf)
		{
			cout << "平衡因子出现异常" << endl;
			return false;
		}
		//需要递归检查是否平衡
		return (leftHight - rightHight <= 1 && leftHight - rightHight >= -1)
			&& _IsBalance(root->_left) && _IsBalance(root->_right);
	}
private:
	Node* _root = nullptr;
};
//void TestAVLTree()
//{
//	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//	//int a[] = { 9,8,7,6,5,4,3,2,1};
//	AVLTree<int, int> t;
//	for (auto e : a)
//	{
//		t.Insert(make_pair(e, e));
//	}
//
//	t.Inorder();
//
//	cout << t.IsBalance() << endl;
//}
void TestAVLTree()
{
	srand((unsigned int)time(0));
	const size_t N = 1000000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand();
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	t.Inorder();

	cout << t.IsBalance() << endl;
}

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

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

相关文章

mycobot 使用教程

(1) 树莓派4B ubuntu系统调整swap空间与使SD卡快速扩容参考&#xff1a;https://www.bilibili.com/read/cv14825069https://blog.csdn.net/weixin_45824920/article/details/114381292?spm1001.2101.3001.6650.1&utm_mediumdistribute.pc_relevant.none-task-blog-2%7Edef…

内推|香港外企急招ETL工程师!数据分析师+Python开发+运营专家

2月已过半还在找工作&#xff1f;快来看看有没有适合你的岗位&#xff01;01公司&#xff1a;友邦科技 工作地点&#xff1a;成都市高新区OCG国际中心招聘岗位&#xff1a;ETL工程师 15-18k该岗位为香港项目&#xff0c;需要有数仓或者大数据经验。本科IT或数据相关专业&#…

深度优先搜索(DFS)-蓝桥杯

一、搜索搜索是“暴力法”算法思想的具体实现。搜索是“通用”的方法。一个问题&#xff0c;如果比较难&#xff0c;那么先尝试一下搜索&#xff0c;或许能启发出更好的算法。技巧:竞赛时遇到不会的难题&#xff0c;用搜索提交一下&#xff0c;说不定部分判题数据很弱&#xff…

8 Flutter UI 之 路由

一 基本路由路由就是页面的跳转&#xff0c;通过Navigator组件管理路由导航Flutter 提供了两种方式 基本路由和命名路由Container(child: Center(child: Column(children: [ElevatedButton(onPressed: () {// 跳转到购物车界面Navigator.of(context).push(MaterialPageRoute(bu…

教你快速学会画动漫人物表情

动漫人物表情画法&#xff0c;3分钟教你快速学会画表情&#xff0c;快来跟我一起零成本学板绘吧&#xff01;咱们的免费板绘系列教程又来啦&#xff0c;今天教大家的板绘技能是什么呢&#xff1f;今天的板绘学习教程来教你如何画动漫女生的表情&#xff01; 板绘动漫女生的表情…

Instruction Tuning:无/少样本学习新范式

作者 | 太子长琴 整理 | NewBeeNLP大家好&#xff0c;这里是NewBeeNLP。今天分享一种简单的方法来提升语言模型的 Zero-Shot 能力——指示&#xff08;或指令&#xff09;微调&#xff08;instruction tuning&#xff09; &#xff0c;在一组通过指示描述的数据集上对语言模型微…

Nodejs和JavaScript的区别

ECMAScript 定义了语法&#xff0c;写javascript和nodejs都必须要遵守变量定义&#xff0c;循环、判断、函数原型和原型链、作用域和闭包、异步 可以看阮一峰老师写的ECMAScript 6 入门 即&#xff1a; 不能操作DOM,不能监听click事件&#xff0c;不能发送ajax请求不能处理…

Java LockSupport学习

面试题: 1、LockSupport为什么可以先唤醒线程后阻塞线程? 因为unpark()获得了一个凭证&#xff0c;之后再调用park()方法&#xff0c;就可以名正言顺的消费凭证&#xff0c;故不会阻塞。 2、LockSupport为什么唤醒两次后阻塞两次&#xff0c;但最终结果还会阻塞线程? 因为凭证…

Android 实现沉浸式全屏

前言 本文总结 Android 实现沉浸式全屏的实现方式。 实现沉浸式全屏 在一些需要全屏显示的场景下,比如玩游戏、看横屏视频的时候,内容全屏,占满窗口的体验会让用户更加沉浸到对内容的消费中,带来好的用户体验。 沉浸式显示具体来说就是如状态栏和导航栏部分的显示效果调…

C#:Krypton控件使用方法详解(第五讲) ——kryptonPanel

今天介绍的Krypton控件中的kryptonPanel&#xff0c;下面开始介绍这个控件的属性&#xff1a;首先要介绍的是这个控件的外观属性&#xff1a;Cursor属性&#xff1a;表示鼠标移动过这个控件的时候&#xff0c;鼠标的显示状态。具体属性值有哪些&#xff0c;如下图所示&#xff…

第一批因ChatGPT坐牢的人,已经上路了

大家好&#xff0c;我是 Jack。 ChatGPT 的火爆有目共睹&#xff0c;有人靠着它赚了第一桶金&#xff0c;也有人靠着它即将吃上第一顿牢饭。 任何一件东西的火爆&#xff0c;总会给一些聪明人带来机会。 艾尔登法环火的时候&#xff0c;一堆淘宝卖魂的&#xff1b;羊了个羊火…

动漫人物眼睛画法

本期的动漫绘画课程教大家来学习动漫人物眼睛画法&#xff0c;结合板绘软件从草稿开始一步步教你画出动漫人物眼睛&#xff0c;不用报动漫培训班也能学会&#xff0c;快来跟着本期的动漫人物眼睛画法教程试试吧&#xff01; 动漫人物眼睛画法步骤教程&#xff1a; 注意&#x…

Linux内核实现完全公平调度算法

Linux 进程调度算法经历了以下几个版本的发展&#xff1a; 基于时间片轮询调度算法。(2.6之前的版本)O(1) 调度算法。(2.6.23之前的版本)完全公平调度算法。(2.6.23以及之后的版本) 完全公平调度算法基本原理 完全公平调度算法 体现在对待每个进程都是公平的&#xff0c;那么…

ChatGPT AI 人工智能 开发路径

ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI研发的聊天机器人程序&#xff0c;于2022年11月30日发布。 推荐以下几个AI 开发学习资源 一、GPTZero AI: GPTZero GPTZero 是普林斯顿大学学生 Edward Tian …

Java爬虫—WebMagic

一&#xff0c;WebMagic介绍WebMagic企业开发&#xff0c;比HttpClient和JSoup更方便一&#xff09;&#xff0c;WebMagic架构介绍WebMagic有DownLoad&#xff0c;PageProcessor&#xff0c;Schedule&#xff0c;Pipeline四大组件&#xff0c;并有Spider将他们组织起来&#xf…

MySQL中JSON数据类型详解

目录 概要及优点 JSON定义 JSON字段的增删改查操作 插入操作 查询操作 修改操作 删除操作 如何对JSON字段创建索引&#xff1f; 加索引查询结果分析&#xff1a; 不加索引查询结果分析&#xff1a; 使用JSON时的注意事项 概要及优点 JSON数据类型是MySQL5.7.8开始支持的…

FlowChartX/Diagramming for ActiveX 4.9.8 Crack

构建完美的图表 如果您的应用程序以 ActiveX 平台为目标&#xff0c;并且您需要实现图表功能&#xff0c;那么您所需要的只是 FlowChartX。它提供了创建、自定义和呈现流程图的所有功能。 ActiveX 图表库&#xff1a;分类图表 图 Diagramming for ActiveX该组件为您提供了一组…

浅谈C++函数重载

C相较于C语言来说,重载是一重大特性,让我们一起简单的回顾一下重载那些事 传送门函数重载是什么为什么有函数重载函数重载是如何实现的总结函数重载是什么 函数重载:是函数的一种特殊情况,C允许在同一作用域中声明几个功能相似的同名函数 这些同名函数的形参列表(参数个数or类…

day19_抽象类丶接口

由来 当我们声明一个几何图形类&#xff1a;圆、矩形、三角形类等&#xff0c;发现这些类都有共同特征&#xff1a;求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又无法给出具体的实现&#xff0c;而是应该交给子类各自…

当遇到国外客户的问题,你解决不了的时候怎么办

对我来说&#xff0c;今年的这个春节假期有点长&#xff0c;差不多休了一个月。复工之后&#xff0c;截止目前做到了60万RMB的业绩&#xff0c;但是相较于往年&#xff0c;整体状态还是差了些。往年的春节&#xff0c;我都是随时待命的状态&#xff0c;整个春节天天坐于电脑前&…