【C++】详解AVL树并模拟实现

这篇具有很好参考价值的文章主要介绍了【C++】详解AVL树并模拟实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  前言:

  •   之前我们为了让数据存储效率提高,引进了二叉搜索树。
  •   但是我们发现,二叉搜索树的时间复杂度还是O(N),因为二叉搜索树并不是非常的平衡。
  •   并不是所有树都是满二叉树,可能出现单边书这样极端的情况,所以我们引进了查找效率更高的AVL树

目录

(一)AVL树的概念

(二)AVL树的模拟实现

(1)AVL树结点的定义

(2)AVL树部分功能的实现

1、查找

2、插入(重点!)

2.1插入结点后平衡因子的变化

2.2情况分析

2.3旋转操作的实现

2.3.1左单旋

2.3.2右单旋

 2.3.3左右双旋

2.3.4右左双旋

(三) 验证AVL树

1、中序遍历

2、判断树是否平衡

(四)总代码详解

 



(一)AVL树的概念

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

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

图示:

【C++】详解AVL树并模拟实现,C++,c++

图中结点外数字代表平衡因子→右子树高度-左子树高度

AVL树又叫高度平衡二叉搜索树。

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 〇(logN)搜索时间复杂度 〇(logN)。


(二)AVL树的模拟实现

(1)AVL树结点的定义

~直接实现key_value的结构 – 三叉链的形式(带父节点)

图示如下: 

【C++】详解AVL树并模拟实现,C++,c++

其中:

_bf-----balance factor,代表平衡因子

右子树与左子树的高度差


(2)AVL树部分功能的实现

1、查找

和二叉搜索树查找功能的实现几乎一致:

从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
最多查找高度次,走到到空,还没找到,这个值不存在。

2、插入(重点!)

  • 这里的插入思路和二叉搜索树中插入的思路一致,找到合适的位置之后再链接

这里链接是比较容易的,但是链接之后对各个结点中的平衡因子的调整则是比较费劲。

所以重点就在于分析各种可能出现平衡因子的情况,然后调整树来解决。

2.1插入结点后平衡因子的变化

1.首先我们考察插入结点和它父亲结点之间的关系变化:

  • 当一个结点的左或者右链接了一个结点,该结点为链接结点的父节点
  • 当该父节点的右边连接上孩子时,此时该父节点的右子树比左子树高了一层,平衡因子_bf + +
  • 当该父节点的左边连接上孩子时,此时该父节点的左子树比右子树高了一层,平衡因子_bf – –

【C++】详解AVL树并模拟实现,C++,c++


2.然后我们再以此为基础考察其他结点的变化情况:

我们以上图为例,8结点作为父亲结点,平衡因子发生了变化,这就有可能继续导致8结点的父亲结点的平衡因子发生变化。

简而言之,插入一个结点真正会影响的是其祖先的平衡因子的改变。

【C++】详解AVL树并模拟实现,C++,c++【C++】详解AVL树并模拟实现,C++,c++


3.处理方法

(1)向上更新:

  • 更新新插入节点祖先的平衡因子
  • 没有违反规则就结束了,违反规则,不平衡了就需要处理
  • 这里的处理是旋转处理(接下来会重点介绍)
  • 在更新的过程中只要是发现违反了AVL树规则的就需要旋转处理

(2)如何向上更新:

  • 更新的方式是沿着祖先路径更新(回溯)
  • 将parent结点更新到它的_parent位置上,将cur结点更新到它的_parent位置上
  • 在这个过程中一旦发现有违反AVL树规则的时即parent的平衡因子变成2或-2
  • 这时就需要进行旋转处理

具体过程如下:

  • 子树高度变了,就要继续往上更新
  • 子树的高度不变, 则更新完成
  • 子树违反平衡规则,则停止更新, 旋转子树

2.2情况分析

1.父亲结点平衡因子为0时,符合规则,break跳出:
【C++】详解AVL树并模拟实现,C++,c++

2.父亲结点平衡因子是1或者-1时继续向上更新:

【C++】详解AVL树并模拟实现,C++,c++

3.更新过程中,父亲结点平衡因子出现了2或者-2,说明子树不平衡了,需要上述说到的旋转处理(下文中会详解旋转的几种情况)

【C++】详解AVL树并模拟实现,C++,c++

看了如上的代码有点抽象,我们下面用具体例子来讲解此情况下不同的情景:

我们也可以明晰的看出上述代码有四个条件语句,每个if其实代表的就是一种情景,我们详细分析:

情景一:

这时我们发现8作为父亲结点时出现了平衡因子为2的情况,此时cur结点平衡因子为1

【C++】详解AVL树并模拟实现,C++,c++
 


情景二:

这时我们发现8作为父亲结点时出现了平衡因子为2的情况,此时cur结点平衡因子为-1

【C++】详解AVL树并模拟实现,C++,c++


情景三,情景四大家可以自己画图啦!其实就是cur所在子树变为parent左子树的一些情况,下面详解旋转操作中将会对这几种情景解释并解决!

4.一定要检查 -- 不保证其他地方不会出现错误
            
    比如插入之前AVL数就存在平衡子树,|平衡因子| >= 2结点。

【C++】详解AVL树并模拟实现,C++,c++


2.3旋转操作的实现

上述我们已经阐述了,在什么情况下需要对AVL树进行旋转操,接下来我们就来讲一下具体的旋转步骤。

旋转原则:

  • 保持搜索树的规则
  • 子树变平衡

旋转一共分为四种旋转方式:

  •  左单旋、右单旋
  •  左右双旋、右左双旋
2.3.1左单旋

当右子树高的时候,这时就要向左旋转。

旋转过程:

  • 将要旋转的子树的根节点设为parent,根结点的右子树为subR,subR的左节点为subRL
  • 将subRL给parent的右,再将parent给subR的左
  • 改变其链接关系即可
  • 这样一来subR做了子树的根,根结点的左右子树高度差从2变成了0

图示:

【C++】详解AVL树并模拟实现,C++,c++

代码图解:(后续代码统一给)

【C++】详解AVL树并模拟实现,C++,c++

2.3.2右单旋

 有左单旋的基础,我们知道右单旋的情况:
当左子树高的时候,这时就要向右旋转。

旋转过程:

  • 将要旋转的子树的根节点设为parent,根结点的左子树为subL,subL的右节点为subLR
  • 将subLR给parent的左,再将parent给subL的右
  • 改变其链接关系即可
  • 这样一来subL做了子树的根,根结点的左右子树高度差从2变成了0

图示:

【C++】详解AVL树并模拟实现,C++,c++

代码图解:

【C++】详解AVL树并模拟实现,C++,c++

 2.3.3左右双旋

在一些情况中,左单旋和右单旋是无法解决问题的,需要二次旋转来降低子树高度,

比如下面的情况:

【C++】详解AVL树并模拟实现,C++,c++

这时候无论怎么单旋,我们都无法降低子树的高度。

所以这里我们要用到双旋。

图示:
【C++】详解AVL树并模拟实现,C++,c++

这样一来,我们就把单次旋转无法降低高度的情况解决了!

详细过程可以跟着图示来理解。

代码图解: 

这里主要是根据subLR平衡因子的不同情况来给降低高度后子树中不同节点的平衡因子赋值,大家可以模仿上面的图示把不同情况画下来!

【C++】详解AVL树并模拟实现,C++,c++

  •  我们在实现双旋的时候可以复用单旋
  •  但是单旋有个坑,会出现将平衡因子搞成0的情况

两种解决方案:

  • 将单旋中更新的平衡因子拿出来
  • 旋转之前将位置记录下来

我们采用第一种方法,单独将平衡因子拿出来处理。

2.3.4右左双旋

和上面的左右双旋的情况类似:

图示:

【C++】详解AVL树并模拟实现,C++,c++

代码图解:
 【C++】详解AVL树并模拟实现,C++,c++


(三) 验证AVL树

上面我们基本完成了AVL树的两大重要的功能实现(删除操作有困难,本文暂不实现,后续深入会讨论),下面我们要验证我们写的树是不是AVL树。

主要验证两大方面:

  • 是不是二叉搜索树(AVL树是特殊的二叉搜索树,中序遍历应该是有顺序的)
  • 子树的高度差是不是符合条件(-2<右子树高度-左子树高度<2)

1、中序遍历

介绍前,我们先说明一点:

  • 我们平时调用类中的成员函数一般是对象.成员函数
  • 比如一个对象t中序历遍就是t.InOrder()
  • 这样符合库中其他类成员函数的使用习惯
  • 但是我们要想中序历遍必须传根节点
  • 所以我们叠加一层嵌套,巧妙解决这个问题:

【C++】详解AVL树并模拟实现,C++,c++

多套一层,这样就可以符合我们使用这些接口函数的习惯了!

其实通过上面的讲解后,中序遍历作者已经给出啦:
我们选择和之前中序历遍搜索二叉树一样的方法,采用递归的方式解决

2、判断树是否平衡

我们一同思考,如何判断左右子树高度差在2以内呢?

  • 首先,我们必须知道左右子树的高度
  • 其次,我们再判断高度差是否是2以内

求树的高度:

其实我们之前也讲解过,采用递归的方法更容易来求解。

思路:树的高度就是左子树或者右子树高的那一颗树的高度加上根节点高度(也就是+1)

【C++】详解AVL树并模拟实现,C++,c++

判断树是否平衡:

我们求解左右子树的高度后记录下来,相减的绝对值看是否是二以内。

当然,对于树中每一个结点我们都要判断他的子树是否符合条件,所以也要用到递归:

【C++】详解AVL树并模拟实现,C++,c++文章来源地址https://www.toymoban.com/news/detail-699319.html

(四)总代码详解


#include<iostream>
#include<assert.h>
#include<algorithm>
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;
	int _bf;//平衡因子

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



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.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->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;


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

			if (parent->_bf == 1 || parent->_bf == -1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 需要旋转处理 -- 1、让这颗子树平衡 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)//左右双旋
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋
				{
					RotateRL(parent);
				}
				//旋转完之后ppNode为根的子树高度不变 -- 所以对ppNode的平衡因子没有影响
				break;
			}
			else // 一定要检查 -- 不保证其他地方不会出现错误
			{
				//插入之前AVL数就存在平衡子树,|平衡因子| >= 2结点
				assert(false);
			}
		}
		return true;
	}


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

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

	int Height()
	{
		return _Height(_root);
	}
private:

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int HeightL = _Height(root->_left);
		int HeightR = _Height(root->_right);

		if (HeightR - HeightL != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		return abs(HeightL - HeightR) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);

		空树也是AVL树
		//if (nullptr == root)
		//	return true;

		计算pRoot节点的平衡因子:即pRoot左右子树的高度差
		//int leftHeight = _Height(root->_left);
		//int rightHeight = _Height(root->_right);

		求差值
		//int diff = rightHeight - leftHeight;

		如果计算出的平衡因子与pRoot的平衡因子不相等,或者
		pRoot平衡因子的绝对值超过1,则一定不是AVL树
		//if (abs(diff) >= 2)
		//{
		//	cout << root->_kv.first << "结点平衡因子异常" << endl;
		//	return false;
		//}

		平衡因子没有异常但是和结点的对不上
		//if (diff != root->_bf)
		//{
		//	//说明更新有问题
		//	cout << root->_kv.first << "结点平衡因子不符合实际" << endl;
		//	return false;
		//}

		pRoot的左和右如果都是AVL树,则该树一定是AVL树
		把自己和自己的左右子树都检查了,递归检查
		//return _IsBalance(root->_left)
		//	&& _IsBalance(root->_right);
	}


	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}



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

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

	//左单旋
	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 (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}

		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}

			subR->_parent = ppnode;
		}

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

	//右单旋
	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;
			_root->_parent = nullptr;
		}

		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}

			subL->_parent = ppnode;
		}

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

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

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}


	void RotateRL(Node* parent)
	{

		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);
		if (bf == 1)
		{
			parent->_bf = -1;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

private:
	Node* _root = nullptr;
};



void Test_AVLTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		/*	if (e == 14)
			{
			int x = 0;
			}*/

		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;
	}

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



void Test_AVLTree2()
{
	srand(time(0));
	const size_t N = 10;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	t.InOrder();

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

到了这里,关于【C++】详解AVL树并模拟实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】详解AVL树的旋转及其插入操作

    二叉搜索树虽然可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法: 当向二叉搜索树中插入新节点后,

    2024年02月15日
    浏览(40)
  • C++【实现AVL树】

    背景 :我们所使用的的二叉搜索树虽然可以提高查找的效率,但如果有序或者接近有序的时候,二叉搜索树会变成一颗单支树,高度一边很高,另一边很低,时间复杂度变为O(n),效率变低。 而AVL树的出现就是解决以上的问题的,当向二叉搜索树中插入新结点后,能保证每个

    2024年02月09日
    浏览(23)
  • AVL树 -- C++实现

    二叉搜索树虽可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数学家 G.M. A delson- V elskii 和 E.M. L andis 在1962年发明了一种解决上述问题的方法: 当向二叉搜索树中插入新结点

    2024年01月21日
    浏览(30)
  • 模拟实现哈希表超详解(C++)

    😇 😇 hello!我是bug。前面几期我们讲解了树的相关结构,今天我们就要进入 哈希表 的学习了: (代码可能会有一点问题,请各位老铁指正 😘 😘 ) 🌵🌵在前面,我们学习了 红黑树、AVL树 的相关性质,他们增删查改的时间复杂度都是O(logN),这个效率已经是比较高的

    2023年04月08日
    浏览(28)
  • C++ string类详解及模拟实现

    目录 【本节目标】  1. 为什么学习string类? 1.1 C语言中的字符串  1.2 面试题(暂不做讲解)  2. 标准库中的string类 2.1 string类(了解)  2.2 string类的常用接口说明(注意下面我只讲解最常用的接口) 3. string类的模拟实现 3.1string类常用成员函数的模拟实现 3.2 浅拷贝和深拷贝   

    2024年03月23日
    浏览(34)
  • 【数据结构】—AVL树(C++实现)

                                                            🎬慕斯主页 : 修仙—别有洞天                                                  💜 本文前置知识:  搜索二叉树                                                       ♈️ 今日夜电波

    2024年02月05日
    浏览(49)
  • 【C++】AVL树的实现及测试

    AVL树 AVL树也叫平衡二叉搜索树,通过旋转解决了搜索二叉树的不确定性,让整颗树趋近于一颗满二叉树。 1.左右都是一颗AVL树 2.平衡因子的绝对值不会超过1 上图的蓝字表示平衡因子,平衡因子=右子树的高度-左子树的高度 AVL树的插入和二叉搜索树一样,小的在左边,大的在

    2024年02月15日
    浏览(31)
  • 【C++初阶】STL详解(四)vector的模拟实现

    本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C++ 🚚代码仓库:小小unicorn的代码仓库🚚 🌹🌹🌹关注我带你学习编程知识 注:为了防止与标准库当中的vector产生命名冲突

    2024年02月05日
    浏览(41)
  • C++ STL库详解:list的详细模拟实现

    在详细学习并学习c++后,我们对stl库的例如vector、list、string都有了详细的了解,对模板的使用以及类和对象都有了熟练的掌握,而实践才是检验真理的唯一标准,在此片博客中,将利用先前学过的各模块知识来对list这个在数据结构中令许多初学者摸不到北,在c++中出场率不

    2024年01月24日
    浏览(46)
  • 数据结构(C++) : AVL树 实现篇

    目录 1.AVL树引入   (1)二叉搜索树缺点   (2)AVL树简介     [1]问题的解决     [2]AVL树的性质 2.AVL树的插入旋转操作   (1)术语解释   (2)左单旋     [1]插入到右侧的左边     [2]插入到右侧的右边   (3)右单旋     [1]插入到左侧的左边     [2]插入到左侧的右边   (4)左右双旋    

    2024年02月05日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包