[共同学习] 平衡二叉树浅见

2023-11-19


二叉搜索树虽然可以缩短查找的效率,但如果 数据有序或接近有序的二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此就有了解决上述问题的方法:平衡二叉树。

平衡二叉树的概念

平衡二叉树(AVL树):当二叉搜索树插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整)。

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

  • 它的左右子树都是AVL树;
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1。

AVL树结点的定义

template<class T>
struct AVLTreeNode{
	AVLTreeNode(const T& val)
		:_left(nullptr), _right(nullptr), _parent(nullptr)
		, _data(val), _bf(0)
		{}

	AVLTreeNode<T>* _left;  //该结点的左孩子
	AVLTreeNode<T>* _right;  //该结点的右孩子
	AVLTreeNode<T>* _parent;  //该结点的双亲
	T _data; 
	int _bf;  //该结点的平衡因子
};

AVL树的插入

以下平衡因子的算法都采用右子树高度-左子树高度
AVL树是在二叉搜索树的基础上引入平衡因子,因此AVL树的插入可以分为两步:
1.按照二叉搜索树的方式插入新结点;
2.调整结点的平衡因子。

如何调整平衡因子
在cur插入前,parent的平衡因子分为三种情况:-1,0,1,分以下两种情况:
1.如果cur插入到parent的左侧,只需要给parent的平衡因子-1;
2.如果cur插入到parent的右侧,只需要给parent的平衡因子+1。

此时,parent的平衡因子可能有三种情况:0,±1,±2,又存在以下三种情况:
1.parent的平衡因子为0,说明插入前parent的平衡因子为±1,插入后被调整为0,满足AVL树性质;
2.parent的平衡因子为±1,说明插入前parent的平衡因子为0,插入后被调整为±1,此时,以parent为根的树的高度增加,需要继续向上更新平衡因子
3.parent的平衡因子为±2,违反了AVL树的性质,需要进行旋转处理

左左:右单旋

新结点插入较高左子树的左侧:
在这里插入图片描述

void _rotateR(Node* parent){
		Node* subL = parent->_left;  //父结点左子树的根结点
		Node* subLR = subL->_right;  //父结点左子树的右子树的根结点

		//右旋:更新结点链接情况
		subL->_right = parent;
		parent->_left = subLR;
		if (subLR != nullptr)
			subLR->_parent = parent;

		if (parent == _root){  //parent是根结点
			_root = subL;
			subL->_parent = nullptr;
		}
		else{
			Node* pparent = parent->_parent;  //记录parent的父结点
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		parent->_parent = subL;

		//更新平衡因子
		parent->_bf = subL->_bf = 0;
}

右右:左单旋

新结点插入较高右子树的右侧:
在这里插入图片描述

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

		subR->_left = parent;
		parent->_right = subRL;
		if (subRL != nullptr)
			subRL->_parent = parent;

		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;
		}
		parent->_parent = subR;

		parent->_bf = subR->_bf = 0;
}

左右:先左旋,再右旋

新结点插入较高左子树的右侧:
在这里插入图片描述

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

		//旋转之前,保留subLR的平衡因子
		int bf = subLR->_bf;

		//旋转完成后,需要根据该平衡因子调整其他结点的平衡因子
		_rotateL(parent->_left);
		_rotateR(parent);

		//更新平衡因子
		if (1 == bf)
			subL->_bf = -1;
		else if (-1 == bf)
			parent->_bf = 1;
}

右左:先右旋,再左旋

新结点插入较高右子树的左侧:
在这里插入图片描述

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

		int bf = subRL->_bf;

		_rotateR(parent->_right);
		_rotateL(parent);

		if (1 == bf)
			parent->_bf = -1;
		else if (-1 == bf)
			subR->_bf = 1;
}

AVL树的验证

验证其为二叉搜索树

如果中序遍历可以得到有序序列,就说明为二叉搜索树。

验证其为平衡树

  • 每个结点子树高度差的绝对值不超过1;
  • 结点的平衡因子计算是否正确。
bool IsBalance(Node* root){
		if (root == nullptr)
			return true;
		//计算root结点的平衡因子
		int leftHeight = _height(root->_left);
		int rightHeight = _height(root->_right);
		int diffH = rightHeight - leftHeight;

		//如果计算出的diffH与root的平衡因子不相等,或者
		//diffH的值大于1或小于-1,则不是AVL树
		if (diffH != root->_bf || ((diffH > 1) || (diffH < -1)))
			return false;

		//如果root的左右子树都是AVL树,则该树一定是AVL树
		return IsBalance(root->_left) && IsBalance(root->_right);
}

int _height(Node* root){
		if (root == nullptr)
			return 0;
		int leftH = _height(root->_left);
		int rightH = _height(root->_right);
		return (leftH > rightH) ? (leftH + 1) : (rightH + 1);
}

AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,这样可以保证查询时高效的时间复杂度(log2N),但如果对AVL树做一些结构修改的操作,性能非常低下。如果需要一种查询高效且有序的数据结构,而且数据个数不会轻易改变,可以考虑AVL树。

AVL树的实现

#include <iostream>
using namespace std;

template<class T>
struct AVLTreeNode{
	AVLTreeNode(const T& val)
		:_left(nullptr), _right(nullptr), _parent(nullptr)
		, _data(val), _bf(0)
		{}

	AVLTreeNode<T>* _left;  //该结点的左孩子
	AVLTreeNode<T>* _right;  //该结点的右孩子
	AVLTreeNode<T>* _parent;  //该结点的双亲
	T _data; 
	int _bf;  //该结点的平衡因子
};

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

	AVLTree() :_root(nullptr){}
	~AVLTree(){
		_destroy(_root);
	}

	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->_data == val)
				return false;
			else if (cur->_data > val)
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		cur = new Node(val);
		if (parent->_data > val)
			parent->_left = cur;
		else
			parent->_right = cur;

		cur->_parent = parent;  //链接parent指针

		//调整双亲结点的平衡因子
		//更新范围:parent结点到根结点
		while (parent){
			if (cur == parent->_left)  //插入到左子树,左子树高度+1
				parent->_bf--;
			else if(cur == parent->_right) //插入到右子树,不能用else
				parent->_bf++;
			//判断parent的平衡因子
			if (0 == parent->_bf)  //parent结点某子树补齐,parent高度不变
				break;
			else if (-1 == parent->_bf || 1 == parent->_bf){  //parent子树高度+1,往上更新
				cur = parent;
				parent = cur->_parent;
			}
			else{  //调整搜索树重写达到平衡

				//左子树的左节点:右旋
				if (-2 == parent->_bf && -1 == cur->_bf)
					_rotateR(parent);

				//右子树的右结点:左旋
				else if (2 == parent->_bf && 1 == cur->_bf)
					_rotateL(parent);

				//左子树的右结点:先左旋,再右旋
				else if (-2 == parent->_bf && -1 == cur->_bf)
					_rotateLR(parent);

				//右子树的左结点:先右旋,再左旋
				else if (2 == parent->_bf && -1 == cur->_bf)
					_rotateRL(parent);
			}
		}
		return true;
	}

	void Inorder(){
		if (IsBalance(_root))
			_inorder(_root);
		else
			cout << "不是AVL树";
		cout << endl;
	}

	bool IsBalance(Node* root){
		if (root == nullptr)
			return true;
		//计算root结点的平衡因子
		int leftHeight = _height(root->_left);
		int rightHeight = _height(root->_right);
		int diffH = rightHeight - leftHeight;

		//如果计算出的diffH与root的平衡因子不相等,或者
		//diffH的值大于1或小于-1,则不是AVL树
		if (diffH != root->_bf || ((diffH > 1) || (diffH < -1)))
			return false;

		//如果root的左右子树都是AVL树,则该树一定是AVL树
		return IsBalance(root->_left) && IsBalance(root->_right);
	}

private:
	void _rotateR(Node* parent){
		Node* subL = parent->_left;  //父结点左子树的根结点
		Node* subLR = subL->_right;  //父结点左子树的右子树的根结点

		//右旋:更新结点链接情况
		subL->_right = parent;
		parent->_left = subLR;
		if (subLR != nullptr)
			subLR->_parent = parent;

		if (parent == _root){  //parent是根结点
			_root = subL;
			subL->_parent = nullptr;
		}
		else{
			Node* pparent = parent->_parent;  //记录parent的父结点
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		parent->_parent = subL;

		//更新平衡因子
		parent->_bf = subL->_bf = 0;
	}

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

		subR->_left = parent;
		parent->_right = subRL;
		if (subRL != nullptr)
			subRL->_parent = parent;

		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;
		}
		parent->_parent = subR;

		parent->_bf = subR->_bf = 0;
	}

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

		//旋转之前,保留subLR的平衡因子
		int bf = subLR->_bf;

		//旋转完成后,需要根据该平衡因子调整其他结点的平衡因子
		_rotateL(parent->_left);
		_rotateR(parent);

		//更新平衡因子
		if (1 == bf)
			subL->_bf = -1;
		else if (-1 == bf)
			parent->_bf = 1;
	}

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

		int bf = subRL->_bf;

		_rotateR(parent->_right);
		_rotateL(parent);

		if (1 == bf)
			parent->_bf = -1;
		else if (-1 == bf)
			subR->_bf = 1;
	}

	void _destroy(Node*& root){
		if (root == nullptr)
			return;
		_destroy(root->_left);
		_destroy(root->_right);
		delete root;
		root = nullptr;
	}

	void _inorder(Node* root){
		if (root == nullptr)
			return;
		_inorder(root->_left);
		cout << root->_data << " ";
		_inorder(root->_right);
	}

	int _height(Node* root){
		if (root == nullptr)
			return 0;
		int leftH = _height(root->_left);
		int rightH = _height(root->_right);
		return (leftH > rightH) ? (leftH + 1) : (rightH + 1);
	}

private:
	Node* _root;
};

感悟

关于感悟是基于自己实现的代码中出现的问题
在判断插入结点是左孩子还是右孩子更新双亲结点的时候,
if (cur == parent->_left) //插入到左子树,左子树高度+1
  parent->_bf--;
else //插入到右子树
  parent->_bf++;
这是最初的代码,用的是else而不是else if,但是在某些特殊情况的插入时,比如已经有结点1和4,要再插入6时,首先会找到parent和cur,
在这里插入图片描述
在cur位置插入6之后会左旋调整,但调整的流程是:移动parent指针,
在这里插入图片描述
当parent指针的平衡因子不满足AVL树的性质时进行旋转,于是就变成了下面这样:
在这里插入图片描述
这样一来满足了AVL树的性质,但cur变成了parent的父结点,如此在进入上面if else语句中会进入parent->_bf++的语句导致死循环而不是跳出循环。

–以上–

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[共同学习] 平衡二叉树浅见 的相关文章

随机推荐

  • c++/cuda并行累计求和

    文章目录 代码 CMakeLists txt 结果 代码 include
  • 设计模式-建造者模式

    文章目录 建造者模式 创建复杂对象的优雅方式 什么是建造者模式 建造者模式的使用场景 优缺点 示例 使用建造者模式创建电脑对象 建造者模式 创建复杂对象的优雅方式 在软件开发中 有时候我们需要创建具有复杂结构和多个组件的对象 直接在客户端代
  • 【Pytorch异常笔记】Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.

    文章目录 异常描述 解决方法 开发环境 异常描述 OMP Error 15 Initializing libiomp5md dll but found libiomp5md dll already initialized OMP Hint
  • git 和远程服务器关联,Git远程服务器连接被拒绝

    我对这个错误感到疯狂 2天后我没有发现我的系统出了什么问题 我相信修复它非常容易 Git远程服务器连接被拒绝 当我尝试使用Git功能我得到的消息 连接到我的git服务器 无法打开连接 主机不存在 致命的 无法从远程存储库中读取 请确保您拥有
  • Android JNI1--C语言基础

    1 include 相当于java的导包操作 例如 include
  • vue中computed的属性对data中的属性赋值为undefined的原因

    场景 我在computed中return了一个值 然后在data中直接将它复制给另一个属性 结果data中的属性值为undefined 代码示例 timer为undefined 原因 在这里很容易想到是执行顺序的问题 computed中的属
  • sql server 中的日期计算,如当天周的第一天,当前月的第一天

    根据给定的日期 计算该日期在本月所在周数 每周的第一天为周日 但是在月末需要与下个月进行衔接 如 图2012年2月份 3月份的1 2 3号为2月份的第4周 而2月份的1 2 3 4为1月份的最后一周 第五周 declare datetime
  • linux线程间通讯----管道、信号

    进程间通讯机制 unix继承 管道 信号 system V IPC对象 共享内存 消息队列 信号灯集 1 管道 管道分为无名管道和有名管道 区别在于创建的管道是否在文件系统中不可见 无名不可见 有名可见 1 无名管道 特点 1 在创建之后再
  • 系统调用详解

    1 系统调用 1 系统调用就是为了让应用程序可以访问系统资源 每个操作系统都会提供一套接口供应用程序使用 这些接口通常通过中断来实现 例如在windows中是0x2E号中断作为系统调用入口 linux使用0x80号中断作为系统的调用的入口
  • IC数字后端

    在 innovus 里面 有时候我们需要控制 tie cell 的 fanout 和 net length 来避免 tie cell 可能出现 max transition 或者 max fanout 的违例 一般来说 只要 fanout
  • typeScritp的高级函数

    1 交叉类型 交叉类型是将多个类型合并为一个类型 这让我们可以把现有的多种类型叠加到一起成为一种类型 它包含了所需的所有类型的特性 例如 Person Serializable Loggable同时是 Person 和 Serializab
  • BSN唐斯斯:区块链是“新基建中的基建”

    5月16日 19 00 陀螺财经特别邀请到国家信息中心智慧城市发展研究中心副主任 区块链服务网络发展联盟常务副秘书长唐斯斯 为大家带来关于新基建下的区块链新机遇的思考 直播主题 新基建下的区块链新机遇 嘉宾 国家信息中心智慧城市发展研究中心
  • 智慧城管项目可行性研究报告

    后台回复 230915 可获得下载资料的方法 点击文后阅读原文 可获得下载资料的方法 欢迎加入智能交通技术群 联系方式 微信号18515441838
  • C语言中volatile关键字详解以及常见的面试问题

    编译器的优化 程序运行的优化可以分为硬件和软件 硬件上是在CPU和内存中间增加cache 来解决CPU和内存之间运行速率差异过大的问题 软件上则分为编译器优化和程序员优化 程序员优化是程序员在编写代码时 对代码的逻辑顺序进行合理安排 提升效
  • WIN10 修改用户下文件夹的名称

    转载note 我是为了解决正当防卫3不能存档 我的用户名当初设置的数字 转载的原因是 走了很多百度知道和经验的弯路 如果有人看到就别走了 我因为走了弯路前弄后弄导致原先的个人数据文件还丢失 只得跳出步骤新建用户 在PE下复制还有的数据 所以
  • 在windows系统安装linux

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 说明 二 步骤 1 利用WindowsADK安装windows 1 Windows 评估和部署工具包 ADK 包括 CopyPE 和 MakeWinPEM
  • no matching host key type found. Their offer: ssh-rsa 问题解决

    最近升级了Mac OS Ventura 13 0 1后发现ssh指定密钥登录服务器失败 no matching host key type found Their offer ssh rsa 进入当前用户的 ssh目录发现比之前系统多了一个
  • Ubuntu20.04安装qemu错误解决方法

    1 如果运行 configure显示没有找到pip sudo apt get install phthon3 pip 2 如果运行 configure显示 ninja error opening build log Permission d
  • com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的区别

    com mysql jdbc Driver 是 mysql connector java 5中的 com mysql cj jdbc Driver 是 mysql connector java 6中的 1 JDBC连接Mysql5 com
  • [共同学习] 平衡二叉树浅见

    平衡二叉树 平衡二叉树的概念 AVL树结点的定义 AVL树的插入 左左 右单旋 右右 左单旋 左右 先左旋 再右旋 右左 先右旋 再左旋 AVL树的验证 验证其为二叉搜索树 验证其为平衡树 AVL树的性能 AVL树的实现 感悟 以上 二叉搜