[C/C++]详解STL容器6--AVL树的介绍及部分模拟实现

Posted TT在长大

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C/C++]详解STL容器6--AVL树的介绍及部分模拟实现相关的知识,希望对你有一定的参考价值。

本文对AVL树进行了介绍,并对其核心功能进行了模拟实现。

目录

一、AVL树的概念

 二、AVL树节点的定义

三、AVL树的插入

四、AVL树的旋转

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

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

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

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

5.总结

五、AVL树的性能

完整代码


一、AVL树的概念

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

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

 可以通过每个节点增加平衡因子(右子树高度 - 左子树高度)来保证高度之差绝对值不超过1。

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

 二、AVL树节点的定义

AVL树节点的定义:

template<class K, class V>
struct AVLTreeNode

	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf; //平衡因子

	pair<K, V> _kv;		//使用结构模板将两个数据打包为一个数据

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

三、AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 先按照二叉搜索树的规则将节点插入到AVL树中;
  2. 调整节点的平衡因子,新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性。

cur插入后,parent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

  1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可;
  2.  如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可;

此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2

  1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功;
  2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负此时以pParent为根的树的高度增加,需要继续向上更新;
  3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理。

 具体代码如下(旋转部分在下一部分介绍):

	bool Insert(const pair<K, V> kv)
	
		if (_root == nullptr)
		
			_root = new Node(kv);

			return ture;
		


		//1.找到目标位置并插入新节点
		Node* parent = _root, cur = _root;
		while (cur)
		
			if (cur->_kv.first > kv.first)
			
				parent = cur;
				cur = cur->_left;
			
			else if (cur->_kv.first < kv.first)
			
				parent = cur;
				cur = cur->_right;
			
			else
			
				return false;
			
		

		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		
			parent->_right = cur;
			cur->_parent = parent;
		
		else
		
			parent->_left = cur;
			cur->_parent = parent;
		

		//控制平衡
		//1.更新平衡因子
		//2.不平衡则旋转
		while (cur != _root)
		
			if (parent->_left == cur) 			//处理,左加右减
				parent->_bf++;	
			
			else 
				parent->_bf--;
			

			if (parent->_bf == 0) 
				break;						//原本 = -1 / 1,且加到了另一侧空的地方,高度没有变化
			
			else if(parent->_bf == 1 || parent->_bf == -1)
			
				//高度发生变化,需要继续改变父节点
				cur = parent;
				parent = parent->_parent;
			
			else if (parent->_bf == 2 || parent->_bf == -2)
			
				//需要旋转
			
		

		return true;
	

四、AVL树的旋转

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

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

上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:

  1. 30节点的右孩子可能存在,也可能不存在;
  2. 60可能是根节点,也可能是子树。如果是根节点,旋转完成后,要更新根节点,如果是子树,可能是某个节点的左子树,也可能是右子树。

代码如下:

	void RotateR(Node* parent)
	
		Node* subL = parent->_left;
		Node* subLR = subL->_left;
		Node* parentP = parent->_parent;

		if (subLR)							//左子树的右子树连接到父的右
			subLR->_parent = parent;		
		
		parent->_left = subLR;
		subL->_right = parent;
		parent->_parent = subL;

		// 如果parent是根节点,根新指向根节点的指针
		if (parent == _root)
		
			_root = subL;
			subL->_parent = nullptr;
		
		else
		
			// 如果parent是子树,可能是其双亲的左子树,也可能是右子树
			if (parentP->_left == parent)
				parentP->_left = subL;
			else
				parentP->_right = subL;

			subL->_parent = parentP;
		

		// 根据调整后的结构更新部分节点的平衡因子
		subL->_bf = parent->_bf = 0;
	

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

逻辑可参考1,这里直接给出代码:

void RotateL(Node* parent)
	
		Node* subR = parent->_left;
		Node* subRL = subL->_left;
		Node* parentP = parent->_parent;

		if (subRL)
			subRL->_parent = parent;

		parent->_right = subRL;
		subR->_left = parent;
		parent->_parent = subR;

		// 如果parent是根节点,根新指向根节点的指针
		if (parent == _root)
		
			_root = subR;
			subR->_parent = nullptr;
		
		else
		
			// 如果parent是子树,可能是其双亲的左子树,也可能是右子树
			if (parentP->_left == parent)
				parentP->_left = subR;
			else
				parentP->_right = subR;

			subR->_parent = parentP;
		

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

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

 将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。

旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整.

 平衡因子更新三种情况:

  1. b子树高度变化为h+1,引发双旋;
  2. c子树高度变化为h+1,引发双旋;
  3. h == 0, a,b,c,d子树都存在,60是新增。

代码如下:

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

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

		if (bf == -1)				//情况一:
		
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		
		else if (bf == 1)				//情况二:
		
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		
		else if (bf == 0)			//情况三:
		
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		
		else
		
			assert(false);
		
	

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

代码如下:

void RotateRL(Node* parent)
	
		Node* subR = parent->_right;
		Node* subRL = subL->_left;
		int bf = subR->_bf;	//旋转之前,保存SubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子

		RotateR(parent->_right);		//先右
		RotateL(parent);

		//更新平衡因子
		if (bf == 1)				//情况一:
		
			subL->_bf = 0;
			parent->_bf = -1;
			subLR->_bf = 0;
		
		else if (bf == -1)				//情况二:
		
			parent->_bf = 0;
			subL->_bf = 1;
			subLR->_bf = 0;
		
		else if (bf == 0)			//情况三:
		
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		
		else
		
			assert(false);
		
	

5.总结

 假如以Parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑 

  1. Parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为SubR
    1. 当SubR的平衡因子为1时,执行左单旋
    2. 当SubR的平衡因子为-1时,执行右左双旋
  2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为SubL
    1. 当SubL的平衡因子为-1是,执行右单旋
    2. 当SubL的平衡因子为1时,执行左右双旋

旋转完成后,原parent为根的子树高度降低,已经平衡,不需要再向上更新。

五、AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

完整代码

#pragma once
#include <iostream>
#include <assert.h>

using namespace std;

template<class K, class V>
struct AVLTreeNode

	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf; //平衡因子

	pair<K, V> _kv;		//使用结构模板将两个数据打包为一个数据

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


template<class K, class V>
class AVLTree

	typedef AVLTreeNode<K, V> Node;
private:
	Node* _root;

	void _Destory(Node* root)
	
		if (root == nullptr)
		
			return;
		
		_Destory(root->_left);
		_Destory(root->_right);

		delete root;
	

	void RotateR(Node* parent)
	
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentP = parent->_parent;

		if (subLR)							//左子树的右子树连接到父的右
			subLR->_parent = parent;		
		
		parent->_left = subLR;
		subL->_right = parent;
		parent->_parent = subL;

		// 如果parent是根节点,根新指向根节点的指针
		if (parent == _root)
		
			_root = subL;
			subL->_parent = nullptr;
		
		else
		
			// 如果parent是子树,可能是其双亲的左子树,也可能是右子树
			if (parentP->_left == parent)
				parentP->_left = subL;
			else
				parentP->_right = subL;

			subL->_parent = parentP;
		

		// 根据调整后的结构更新部分节点的平衡因子
		subL->_bf = parent->_bf = 0;
	

	void RotateL(Node* parent)
	
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentP = parent->_parent;

		if (subRL)
			subRL->_parent = parent;

		parent->_right = subRL;
		subR->_left = parent;
		parent->_parent = subR;

		// 如果parent是根节点,根新指向根节点的指针
		if (parent == _root)
		
			_root = subR;
			subR->_parent = nullptr;
		
		else
		
			// 如果parent是子树,可能是其双亲的左子树,也可能是右子树
			if (parentP->_left == parent)
				parentP->_left = subR;
			else
				parentP->_right = subR;

			subR->_parent = parentP;
		

		subR->_bf = parent->_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)				//情况一:
		
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		
		else if (bf == 1)				//情况二:
		
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		
		else if (bf == 0)			//情况三:
		
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		
		else
		
			assert(false);
		
	

	void RotateRL(Node* parent)
	
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;	//旋转之前,保存SubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子

		RotateR(parent->_right);		//先右
		RotateL(parent);

		//更新平衡因子
		if (bf == 1)				//情况一:
		
			subR->_bf = 0;
			parent->_bf = -1;
			subRL->_bf = 0;
		
		else if (bf == -1)				//情况二:
		
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		
		else if (bf == 0)			//情况三:
		
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		
		else
		
			assert(false);
		
	

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

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	

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

		int left = _Height(root->_left);
		int right = _Height(root->_right);

		return right > left ? right + 1 : left + 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 false;
		

		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	

public:
	AVLTree()
		:_root(nullptr)
	

	~AVLTree()
	
		_Destory(_root);

		_root = nullptr;
	

	V& operator[](const K& key)
	
		pair<Node*, bool> ret = Insert(make_pair(key, V()));
		return ret.first->_kv.second;
	

	pair<Node*, bool> Insert(const pair<K, V> kv)
	
		if (_root == nullptr)
		
			_root = new Node(kv);

			return make_pair(_root, true);
		


		//1.找到目标位置并插入新节点
		Node* parent = _root,* cur = _root;
		while (cur)
		
			if (cur->_kv.first > kv.first)
			
				parent = cur;
				cur = cur->_left;
			
			else if (cur->_kv.first < kv.first)
			
				parent = cur;
				cur = cur->_right;
			
			else
			
				return make_pair(cur, true);
			
		

		cur = new Node(kv);
		Node* newnode = cur;
		if (parent->_kv.first < kv.first)
		
			parent->_right = cur;
			cur->_parent = parent;
		
		else
		
			parent->_left = cur;
			cur->_parent = parent;
		

		//控制平衡
		//1.更新平衡因子
		//2.不平衡则旋转
		while (cur != _root)
		
			if (parent->_left == cur) 			//处理
				parent->_bf--;	
			
			else 
				parent->_bf++;
			

			if (parent->_bf == 0) 
				break;						//原本 = -1 / 1,且加到了另一侧空的地方,高度没有变化
			
			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)			//左边高
				
					if (parent->_bf == -1)
					
						//左边高,右单旋
						RotateR(parent);
					
					else
					
						RotateLR(parent);
					
				
				else
				
					if (parent->_bf == 1)
					
						//左边高,右单旋
						RotateL(parent);
					
					else
					
						RotateRL(parent);
					
				
			
			else
			
				assert(false);
			
		

		return make_pair(newnode, true);
	

	Node* Find(const K& key)
	
		Node* cur = _root;
		while (cur)
		
			if (cur->_kv.first < key)
			
				cur = cur->_right;
			
			else if (cur->_kv.first > key)
			
				cur = cur->_left;
			
			else
			
				return cur;
			
		

		return nullptr;
	

	void InOrder()
	
		_InOrder(_root);
	

	bool IsAVLTree()
	
		return _IsBalance(_root);
	

	bool Erase(const K& key)
	
		return false;
	
;

test.cpp

#include"AVLTree.h"
#include<string>

void TestAVLTree()

	//int a[] =  1, 3, 5, 7, 6 ;
	//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> t;
	for (auto e : a)
	
		t.Insert(make_pair(e, e));
		cout << "" << e << "->" << t.IsAVLTree() << endl;
	

	t.InOrder();
	cout << t.IsAVLTree() << endl;
	t.InOrder();
	t[3] *= 10;
	t[4] *= 10;
	t[5] *= 10;
	t.InOrder();

	AVLTree<string, string> dict;
	dict.Insert(make_pair("sort", ""));
	dict.Insert(make_pair("left", ""));
	dict.InOrder();

	dict["left"] = "+";
	dict["string"] = "ַ";
	dict.InOrder();


int main()

	TestAVLTree();
    printf("谨以此篇献给奶奶!愿她在天堂一切安好");    
    return 0;


结果:

以上是关于[C/C++]详解STL容器6--AVL树的介绍及部分模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器5--二叉搜索树的介绍及模拟实现

[C/C++]详解STL容器5--二叉搜索树的介绍及模拟实现