IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> AVL树的模拟实现 -> 正文阅读

[数据结构与算法]AVL树的模拟实现

AVL树的概念

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

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

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

在这里插入图片描述

AVL树节点的定义

AVL树的结点比二叉搜索树的结点多了指向父结点的指针和储存平衡因子的变量

template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	pair<K, V> _kv;

	int _bf;// 平衡因子=右树高度-左树高度

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{ }
};

AVL树的插入

AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入结点
  2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性

平衡因子的操作如下

子结点插入后,父结点的平衡因子需要调整,在插入之前,父结点的平衡因子为三种情况之一:-1,0, 1, 插入又分以下两种情况

  1. 子结点插入到父节点的右方,父节点的平衡因子加1
  2. 子结点插入到父节点的左方,父节点的平衡因子减1

插入结点后,父结点的平衡因子可能为0,正负1,正负2

  • 如果父节点的平衡因子为0,说明插入之前父节点的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功。
  • 如果父节点的平衡因子为正负1,说明插入前父节点的平衡因子一定为0,插入后被更新成正负1,此时以父节点为根的树的高度增加,需要继续向上更新。
  • 如果父节点的平衡因子为正负2,则父节点的平衡因子违反平衡树的性质,需要对其进行旋转处理。
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.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 < cur->_kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}

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

		// 检查父亲的平衡因子
	
		// 1、父亲所在子树的高度不变,不影响祖先,更新结束
		if (parent->_bf == 0)
		{
			break;
		}// 2、父亲所在子树的高度变了,继续往上更新
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}// 3、父亲所在子树的出现了不平衡,需要旋转处理
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
				// 旋转更新结点
		}
		else
		{
			assert(false);
		}
	}

	return true;
}

AVL树的旋转

在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡。根据节点插入位置的不同,AVL树的旋转分为四种。

右单旋

新节点插入较高左子树的左侧,如图
在这里插入图片描述

void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	// 排除结点为空的情况
	if (subLR)
		subLR->_parent = parent;

	// 记录根结点的父节点
	Node* ppNode = parent->_parent;

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

	// 更新子树的根结点
	if (parent==_root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
			ppNode->_left = subL;
		else
			ppNode->_right = subL;

		subL->_parent = ppNode;
	}

	// 调整平衡因子
	parent->_bf = subL->_bf = 0;		
}

左单旋

新节点插入较高右子树的右侧,如图
在这里插入图片描述

void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	parent->_right = subRL;
	// 排除结点为空的情况
	if (subRL)
		subRL->_parent = parent;

	// 记录根结点的父节点
	Node* ppNode = parent->_parent;
	subR->_left = parent;
	parent->_parent = subR;

	// 更新子树的根结点
	if (parent==_root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
			ppNode->_left = subR;
		else
			ppNode->_right = subR;

		subR->_parent = ppNode;
	}

	// 调整平衡因子
	subR->_bf = parent->_bf = 0;
}

先左单旋再右单旋

新节点插入较高左子树的右侧

在这里插入图片描述
这里可以直接调用上文的左单旋和右单旋,但是需要注意平衡因子的调整

这里分又为3种情况

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	// 左单旋
	RotateL(parent->_left);
	// 右单旋
	RotateR(parent);

	// 调节平衡因子
	if (bf == -1)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 1)
	{
		subL->_bf = -1;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 0)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

先右单旋再左单旋

新节点插入较高右子树的左侧

在这里插入图片描述

这里也分3中情况和上文类似

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

	// 右单旋
	RotateR(parent->_right);
	// 左单旋
	RotateL(parent);

	// 调节平衡因子
	if (bf == 1)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

总结调节平衡因子的情况

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

	// 检查父亲的平衡因子

	// 1、父亲所在子树的高度不变,不影响祖先,更新结束
	if (parent->_bf == 0)
	{
		break;
	}// 2、父亲所在子树的高度变了,继续往上更新
	else if (parent->_bf == 1 || parent->_bf == -1)
	{
		cur = parent;
		parent = parent->_parent;
	}// 3、父亲所在子树的出现了不平衡,需要旋转处理
	else if (parent->_bf == 2 || parent->_bf == -2)
	{
		if (parent->_bf == -2 && cur->_bf == -1)
		{
			// 右旋
			RotateR(parent);
		}
		else if (parent->_bf == 2 && cur->_bf == 1)
		{
			// 左旋
			RotateL(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);
	}
}

AVL树的验证

为了检验AVL的正确性我们添加检验平衡因子的功能

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

	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
	
bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;

	// 记录左右子树的高度
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);
	// 不满足条件报错
	if (rightHeight - leftHeight != root->_bf)
	{
		cout << "平衡因子异常:" << root->_kv.first << endl;
	}

	// 判断其左右子树是否满足条件
	return abs(rightHeight - leftHeight) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}

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

AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-02-26 11:55:08  更:2022-02-26 11:56:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 16:46:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码